0x06.Shader函数mod
在着色器(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
函数时,有几点需要你注意:
-
避免除以零的情况:当
y
的值为 0 时,mod(x, y)
会产生未定义的结果,所以在使用该函数时,一定要确保y
不为 0。 -
处理负数输入:在处理负数输入时,不同编程语言中的
mod
函数可能会有不同的行为。不过在大多数着色器语言里,mod
函数遵循的规则是:mod(-x, y) = mod(x, y)
。 -
性能方面的考虑:虽然
mod
函数的计算成本相对较低,但在对性能要求很高的场景中,还是要谨慎使用,尤其是在处理复杂的向量操作时更需注意。 -
向量操作:在着色器语言中,
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 组合(棋盘格等复杂图案)
结合 step
和 mod
可以创建更复杂的重复图案,例如棋盘格:
float pattern = step(0.5, mod(floor(TexCoord * 10.0).x + floor(TexCoord * 10.0).y, 2.0));
4. smoothstep + fract(平滑过渡的重复)
使用 fract
和 smoothstep
实现平滑过渡的重复图案:
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 控制重复区域
通过 ceil
或 floor
函数限制重复范围:
vec2 clampedUV = TexCoord * repeatCount;
clampedUV = clamp(clampedUV - floor(clampedUV), 0.0, 1.0); // 限制在 [0,1] 内
9. mix + fract(渐变重复)
结合 mix
和 fract
实现颜色渐变的重复:
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 个单位重复一次
总结
不同函数适用于不同场景:
- 简单重复:优先使用
fract
或mod
。 - 镜像对称:使用
abs
和sign
组合。 - 平滑过渡:结合
smoothstep
和fract
。 - 纹理采样:直接利用纹理的
wrapMode
(如GL_REPEAT
)。