#unity3d #shader #modulo #hlsl
#unity3d #шейдер #modulo #hlsl
Вопрос:
Я создал следующий фрагментный шейдер, который _Size
с frac
помощью функции создает сетку плиток определенного размера и рисует небольшую разделительную линию между каждым тайлом, я сохраняю идентификатор тайла в его uv.z
значении, чтобы позже я мог адресовать тайл на основе его идентификатора ( uv.z
).
_Size
и _CurrentID
можно настроить с помощью инспектора
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 amp;amp; uv.x < 1) || (uv.y > .98 amp;amp; 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
}
}
}
(обратите внимание, что сетка начинается с (0,0) нижнего левого по (5,5) верхний правый угол)
Чтобы убедиться, что мои идентификаторы настроены правильно, я перебрал каждое uv.z
значение с помощью floor из _CurrentID
набора из инспектора, который подсвечивает каждую плитку один раз при переходе от 0 к 24 (включительно), как и ожидалось.
if (uv.z == _CurrentID)
{
col = float4(0, 1, 1, 1);
}
пример _CurrentID = 7
подсветки 8-го тайла, как ожидалось
Теперь простое использование _CurrentID
означало бы, что я могу просмотреть каждую плитку только один раз. Чтобы сделать это повторяемым независимо от того, насколько большой _CurrentID
я должен иметь возможность использовать fmod
(по модулю) (хотя то же самое происходит с использованием оператора % modulo) на _CurrentID
, чтобы он возвращался к 0, когда CurrnetID = 25
. Что я (пытаюсь) сделать, используя следующий фрагмент кода:
if (uv.z == fmod(_CurrentID, ((_Size) * (_Size))))
{
col = float4(0, 1, 1, 1);
}
Это хорошо подходит для первой строки (когда _CurrentId
>= 0 amp;amp; < 5). Однако, как только я нажимаю, _CurrentID = 5
все начинает ломаться, поскольку ни одна плитка не загорится, несмотря на то, что ранее была возможность подтвердить, что _CurrentID = 5
загорится плитка в grid (0, 1). Когда я устанавливаю _CurrentID = 6
, снова начинает загораться соответствующая плитка (grid pos (1,1)), которая продолжается там, где grid (0, n) никогда не будет загораться, где n больше 0.
Пример _CurrentID = 5
использования fmod.
Вещи начинают ломаться еще больше, как только мой CurrentID
поднимается выше 25, где, похоже, он вообще не зацикливается по модулю. Как видно из этого gif-файла gyazo. Кажется, что он просто загорается случайными фрагментами.
Начиная сомневаться в себе, я дважды проверил математику по модулю на WolframpAlpha, которая кажется правильной.
Я могу «решить» проблему, из-за которой он пропускает первую плитку каждой строки, выполнив fmod(_CurrentID, ((_Size 1) * (_Size 1)))
, который будет корректно перебирать каждую плитку при первом запуске (включая плитки (0, n)), но теперь мой modulo начинает цикл с 36, после чего он все равно будет загораться случайной плиткой, как показано в gif.
Что я здесь делаю не так?
(Версия Unity 2020.1.1f1, такое же поведение подтверждено в 2019.3.13)
Ответ №1:
Вероятно, это проблема точности с плавающей запятой, поскольку вы сравниваете значения с плавающей запятой для равенства. Вместо этого вы могли бы написать что-то вроде:
float id = _CurrentID % (_Size*_Size);
float epsilon = .0001f;
if (abs(uv.z - id) < epsilon)
{
col = float4(0, 1, 1, 1);
}
Или используйте целые числа для идентификаторов.
Комментарии:
1. Потрясающе! Я не думал о том, что точность с плавающей запятой является проблемой, поскольку я использую оба
_CurrentID
иuv.z
, но я думаю, что даже тогда для этого все еще существует риск.