片段着色器 fmod,为什么不重复

Fragment shader fmod, why is this not repeating

我创建了以下片段着色器,它使用 frac 函数创建了一个大小为 _Size 的图块网格,并在每个图块之间绘制了一条小分隔线,我将图块的 ID 保存在它的 uv.z 值,这样我以后可以根据它的 id (uv.z) 来访问磁贴。

_Size_CurrentID可以通过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
        }
    }
}

(注意网格从左下角(0,0)开始到右上角(5,5))

为了确定我的 ID 设置正确,我循环遍历了每个 uv.z 值和检查员设置的 _CurrentID 的底线,当从 0 开始时,它会点亮每个瓷砖一次达到预期的 24(含)。

if (uv.z == _CurrentID)
{
    col = float4(0, 1, 1, 1);
}


_CurrentID = 7 按预期点亮第 8 个图块的示例

现在只使用 _CurrentID 就意味着我只能遍历每个图块一次。无论 _CurrentID 有多大,为了使其可重复,我应该能够在 _CurrentID 上使用 fmod (模数)(尽管使用 % 模数运算符也会发生同样的情况)所以它循环回来CurrnetID = 25 时为 0。我(尝试)使用以下代码来做:

if (uv.z == fmod(_CurrentID, ((_Size) * (_Size))))
{
    col = float4(0, 1, 1, 1);
}

这对第一行很顺利(当 _CurrentId >= 0 && < 5 时)。然而,一旦我点击 _CurrentID = 5,事情就开始崩溃了,因为没有瓷砖会亮起,尽管之前能够确认 _CurrentID = 5 会点亮网格 (0, 1) 处的瓷砖。当我设置 _CurrentID = 6 时,正确的图块再次开始点亮(网格位置(1,1)),在网格(0,n)永远不会点亮的地方继续点亮,其中 n 大于 0。


_CurrentID = 5 使用 fmod 的示例。

一旦我的 CurrentID 高于 25,事情就开始变得更糟,它似乎根本没有模循环。 As seen in this gyazo gif。它似乎只是随机点亮瓷砖。

开始怀疑自己,我仔细检查了 WolframpAlpha 上的模数学,这似乎是正确的。

我可以通过执行 fmod(_CurrentID, ((_Size + 1) * (_Size + 1))) 来“解决”它跳过每一行的第一个图块的问题,这将正确循环遍历第一个 运行 上的每个图块(包括 (0, n) tiles), 但现在我的模从 36 开始循环,之后它仍会点亮随机图块,如 gif 中所示。

我做错了什么?

(Unity 版本 2020.1.1f1,在 2019.3.13 中确认了相同的行为)

这可能是一个浮点精度问题,因为您正在比较浮点数是否相等。 你可以这样写:

float id = _CurrentID % (_Size*_Size);
float epsilon = .0001f;
if (abs(uv.z - id) < epsilon)
{
    col = float4(0, 1, 1, 1);
}

或者使用整数作为 ID。