I created the following fragment shader that creates a tile grid of size _Size
using the frac
function and draws a small seperator line in between each tile, I save the ID of the tile in its uv.z
value so I can later adres the tile based on its id (uv.z
).
_Size
and _CurrentID
can be adjusted through the inspector
Shader "Unlit/Fractals"
{
Properties
{
[HideInInspector] _MainTex ("Texture", 2D) = "white" {}
_Size ("Size", float) = 5
_CurrentID ("ID", float) = 0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _Size;
float _CurrentID;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
_CurrentID = floor(_CurrentID);
//Create a tile grid that is of _Size * _Size (5 in example), and create an ID for it in the .z value based on its grid position
float3 uv = float3(frac(i.uv * _Size), (floor(i.uv.y * _Size) * _Size) + (floor(i.uv.x * _Size)));
//Create lines to seperate the tiles
float4 col = float4(1, 1, 1, 1);
if ((uv.x > 0.98 && uv.x < 1) || (uv.y > .98 && uv.y < 1))
{
col *= float4(uv.x, uv.y, 0, 1);
}
else
{
col = float4(0, 0, 0, 1);
}
//Loop through all the tiles based on the ID
if (uv.z == fmod(_CurrentID, ((_Size) * (_Size))))
{
col = float4(0, 1, 1, 1);
}
//This correctly goes through every grid tile once, confirming that uv grid ID 5 corresponds to grid position (0,1)
/*if (uv.z == _CurrentID)
{
col = float4(0, 1, 1, 1);
}*/
return col;
}
ENDCG
}
}
}
(note that the grid starts at (0,0) bottom left to (5,5) top right)
To ascertain that my ID's are set up correct I looped through each uv.z
value with the floor of the _CurrentID
set from the inspector, which lights up every tile once, when going from 0 to 24 (inclusive) as expected.
if (uv.z == _CurrentID)
{
col = float4(0, 1, 1, 1);
}
example of _CurrentID = 7
lighting up the 8th tile as expected
Now just using the _CurrentID
would mean I can only go through every tile once. To make this repeatable regardless of how big _CurrentID
is I should be able to use fmod
(modulo) (although the same happens using the % modulo operator) on the _CurrentID
so it loops back to 0 when CurrnetID = 25
. Which I (try to) do using the following piece of code:
if (uv.z == fmod(_CurrentID, ((_Size) * (_Size))))
{
col = float4(0, 1, 1, 1);
}
This goes well for the first row (when _CurrentId
>= 0 && < 5). However once I hit _CurrentID = 5
things start to break, as no tile will light up, despite previously being able to confirm that _CurrentID = 5
will light up the tile at grid (0, 1). When I set _CurrentID = 6
the proper tile starts lighting up again (grid pos (1,1)) which continues where grid (0, n) won't ever light up where n is greater than 0.
Example of _CurrentID = 5
using fmod.
Things start breaking even more once my CurrentID
goes higher than 25, where it doesn't seem to modulo loop around at all. As seen in this gyazo gif. It just seems to light up random tiles.
Starting to doubt myself I double checked the modulo maths on WolframpAlpha, which seems correct.
I can "solve" the issue where it skips the first tile of every row by doing fmod(_CurrentID, ((_Size + 1) * (_Size + 1)))
, which will loop correctly through each tile on the first run (including the (0,n) tiles), but now my modulo starts looping at 36, after which it will still light up a random tile as shown in the gif.
What am I doing wrong here?
(Unity version 2020.1.1f1, same behavior confirmed in 2019.3.13)
It's probably a floating point precision issue since you are comparing floats for equality. Instead of doing that you could write something like:
float id = _CurrentID % (_Size*_Size);
float epsilon = .0001f;
if (abs(uv.z - id) < epsilon)
{
col = float4(0, 1, 1, 1);
}
Or use ints for ids.