在着色器(Shader)编程里,mod是个很常用的函数,它的作用是取模运算,也就是求余数。该函数在图形渲染时能发挥重要作用,像实现图案重复、动画循环这类效果都可能会用到它。


注意: 使用 fmod% 替代 mod**
Unity Shader 基于 HLSL 语法,而不是 GLSL。在 HLSL 中:

  • 浮点数取模:使用 fmod(x, y) 函数。
  • 整数取模:使用 % 运算符。

基本语法

在不同的着色器语言中,mod函数的语法会有一些差异,下面是常见的几种形式:

  • GLSL(OpenGL Shading Language)

    float mod(float x, float y);
    vec2 mod(vec2 x, vec2 y);
    vec3 mod(vec3 x, vec3 y);
    // 其他向量类型以此类推
    
  • HLSL(High-Level Shading Language)

    float mod(float x, float y);
    float2 mod(float2 x, float2 y);
    float3 mod(float3 x, float3 y);
    // 其他向量类型以此类推
    
  • Metal Shading Language

    float mod(float x, float y);
    float2 mod(float2 x, float2 y);
    float3 mod(float3 x, float3 y);
    // 其他向量类型以此类推
    

功能说明

mod(x, y)的计算结果是x除以y得到的余数,其数学表达式为:

mod(x, y) = x - y * floor(x / y)

这里的floor函数是向下取整的意思。

常见应用场景

下面列举一些mod函数在着色器编程中的常见应用场景:

1. 实现纹理或图案的重复效果

当你想要在一个较大的区域内重复使用某个小纹理或者图案时,可以借助mod函数对纹理坐标进行处理,从而实现这一效果。

// 假设 uv 是纹理坐标(范围是 0 到 1)
vec2 tiledUV = mod(uv * 4.0, 1.0); // 把纹理在两个方向上都重复 4 次
vec4 color = texture2D(myTexture, tiledUV);

2. 实现动画循环效果

在制作动画时,为了让动画能够循环播放,可以使用mod函数将时间控制在一个固定的周期内。

// 假设 time 是全局时间变量
float loopTime = mod(time, 5.0); // 使动画每 5 秒循环一次
float animationProgress = loopTime / 5.0; // 得到一个在 0 到 1 之间循环变化的值

3. 创建棋盘格等规律性图案

通过对坐标进行取模运算,能够轻松创建出像棋盘格这样有规律的图案。

// 判断当前位置是在棋盘格的白色区域还是黑色区域
float checker = mod(floor(uv.x * 10.0) + floor(uv.y * 10.0), 2.0);
vec3 color = checker > 0.5 ? vec3(1.0) : vec3(0.0); // 生成黑白棋盘格图案

4. 实现周期性变化的效果

利用mod函数可以实现颜色、位置等属性按照一定周期进行变化的效果。

// 让颜色在红色和绿色之间周期性变化
vec3 pulsatingColor = mix(
    vec3(1.0, 0.0, 0.0), // 红色
    vec3(0.0, 1.0, 0.0), // 绿色
    mod(time, 2.0) / 2.0 // 每 2 秒完成一次颜色过渡
);

使用注意事项

在使用mod函数时,有几点需要你注意:

  1. 避免除以零的情况:当y的值为 0 时,mod(x, y)会产生未定义的结果,所以在使用该函数时,一定要确保y不为 0。

  2. 处理负数输入:在处理负数输入时,不同编程语言中的mod函数可能会有不同的行为。不过在大多数着色器语言里,mod函数遵循的规则是:mod(-x, y) = mod(x, y)

  3. 性能方面的考虑:虽然mod函数的计算成本相对较低,但在对性能要求很高的场景中,还是要谨慎使用,尤其是在处理复杂的向量操作时更需注意。

  4. 向量操作:在着色器语言中,mod函数可以直接对向量进行操作,也就是对向量的每个分量分别进行取模运算。


在着色器编程中,除了 mod 函数外,还有其他几种方法和函数可以实现图案重复效果。

1. fract 函数(小数部分)

fract(x) 函数返回 x 的小数部分,效果类似于 mod(x, 1.0),但更简洁。

vec2 tiledUV = fract(TexCoord * repeatCount);  // 等同于 mod(TexCoord * repeatCount, 1.0)

2. floor 函数(整数除法取余)

通过手动计算余数实现重复效果:

vec2 tiledUV = TexCoord * repeatCount - floor(TexCoord * repeatCount);

3. step + mod 组合(棋盘格等复杂图案)

结合 stepmod 可以创建更复杂的重复图案,例如棋盘格:

float pattern = step(0.5, mod(floor(TexCoord * 10.0).x + floor(TexCoord * 10.0).y, 2.0));

4. smoothstep + fract(平滑过渡的重复)

使用 fractsmoothstep 实现平滑过渡的重复图案:

float t = fract(time * 0.5);  // 0~1 周期变化
float value = smoothstep(0.0, 1.0, t);  // 平滑过渡

5. sin/cos 函数(周期性变化)

通过三角函数实现颜色或位置的周期性变化:

vec3 color = vec3(
    sin(TexCoord.x * 10.0),  // 水平方向颜色波动
    cos(TexCoord.y * 10.0),  // 垂直方向颜色波动
    0.5
);

6. texture2D 的 wrapMode(纹理采样方式)

在纹理采样时,通过设置纹理的 wrapMode 参数(如 REPEAT)让纹理自动重复,无需在着色器中手动计算:

// 假设纹理已设置为 REPEAT 模式
vec4 texColor = texture2D(texture1, TexCoord * repeatCount);

7. sign + abs 组合(镜像重复)

实现镜像对称的重复效果:

vec2 mirroredUV = 1.0 - abs(2.0 * fract(TexCoord * 0.5) - 1.0);

8. ceil/floor 控制重复区域

通过 ceilfloor 函数限制重复范围:

vec2 clampedUV = TexCoord * repeatCount;
clampedUV = clamp(clampedUV - floor(clampedUV), 0.0, 1.0);  // 限制在 [0,1] 内

9. mix + fract(渐变重复)

结合 mixfract 实现颜色渐变的重复:

float t = fract(time);
vec3 color = mix(vec3(1,0,0), vec3(0,0,1), t);  // 红蓝色周期性渐变

10. 位运算(整数重复)

在某些情况下,可以使用位运算(如 &)实现整数级的重复控制:

int tileIndex = int(TexCoord.x * 10.0) & 3;  // 每 4 个单位重复一次

总结

不同函数适用于不同场景:

  • 简单重复:优先使用 fractmod
  • 镜像对称:使用 abssign 组合。
  • 平滑过渡:结合 smoothstepfract
  • 纹理采样:直接利用纹理的 wrapMode(如 GL_REPEAT)。