【免费下载链接】thebookofshaders
Step-by-step guide through the abstract and complex universe of Fragment Shaders.
导读
mod()是 GLSL 中最基础也最高频的数学函数之一,它返回一个数对另一个数取模的余数。在 The Book of Shaders 项目中,mod()不仅是第 05 章"塑形函数(Shaping Functions)"演示的核心工具,更被广泛应用于空间平铺、单元格编号、位解码、噪声周期化与万花筒成像等数十个示例着色器中。读完本文,你将彻底掌握mod()的数学定义、重载形式、边界行为,并学会用它在片段着色器中构建重复图案、交错布局与周期性动画。
本文以仓库 glossary/mod/README.md 的词条为骨架展开,并结合仓库内大量.frag源码佐证其真实用法。
一、函数声明与重载形式
mod()属于 GLSL 内建数学函数,glossary 词条给出了完整的声明形式:
float mod(float x, float y) vec2 mod(vec2 x, vec2 y) vec3 mod(vec3 x, vec3 y) vec4 mod(vec4 x, vec4 y) vec2 mod(vec2 x, float y) vec3 mod(vec3 x, float y) vec4 mod(vec4 x, float y)可见 GLSL 为重载了 7 种签名:向量与向量逐分量取模,或向量与标量混合(标量会被隐式提升为向量并与每个分量运算)。这在处理vec2坐标、vec3颜色、vec4位置数据时非常方便,无需像在 C 语言中那样逐分量手写%循环。
参数语义
x:被求模的值(被除数),即"要对它取模的数";y:模数(除数),即"用来取模的数"。
词条原文的描述为:"Compute value of one parameter modulo another",对应中文即"计算一个参数对另一个参数取模的值"。
二、数学定义:为什么它是"余数"而非"取余"
词条明确给出mod()的计算公式:
mod(x, y) = x - y * floor(x / y)关键在于floor()(向下取整)而非trunc()(向零取整)。这带来两个重要的数学性质:
- 结果符号跟随除数
y。当y为正数时,结果永远落在[0, y)区间;当y为负数时,结果落在(y, 0]区间。这与 C 语言中%运算符(向零取整、符号跟随被除数)行为完全不同,也与 GLSL 中基于trunc的%不同。 - 结果恒等于"除尽后剩下的那一截"。例如
mod(7.3, 2.0)计算为7.3 - 2.0 * floor(7.3 / 2.0) = 7.3 - 2.0 * 3 = 1.3。
这一数学定义让mod()成为"把一维直线折叠成周期性区间"的天然工具,也是后续所有空间平铺技巧的原理基础。
与相邻函数的关系
mod()与仓库词条 floor、fract、ceil 紧密相关(三者互相在 "See Also" 中引用):
| 函数 | 定义 | 结果区间(x > 0 时) | 用途 |
|---|---|---|---|
floor(x) | 不大于 x 的最大整数 | 整数 | 取"格子编号" |
fract(x) | x - floor(x),即小数部分 | [0, 1) | 取"格子内位置" |
ceil(x) | 不小于 x 的最小整数 | 整数 | 向上取整 |
mod(x, y) | x - y * floor(x / y) | [0, y) | 周期折叠、取余 |
特别地,当y = 1.0时,mod(x, 1.0)与fract(x)完全等价——因此词条同时链接了 fract。二者常被互换使用,但mod(x, y)允许任意周期y,通用性更强。
三、第 05 章中的经典演示:周期化塑形
mod()在 The Book of Shaders 正文中的首次核心登场是第 05 章"塑形函数"。在 05/README.md 中,作者提供了可交互的一维函数图:
y = mod(x, 0.5); // return x modulo of 0.5在同一交互面板中,mod与fract、ceil、floor、sign、abs、clamp、min、max一起被列为"额外有用的函数",作者将其比喻为"栅栏",帮助读者掌握一维空间中的往复运动。当y = 0.5时,函数曲线呈现锯齿状周期波形——这正是后续章节中无数平铺图案的最小单元。
第 05 章还收录了 05/easing.frag(Robert Penner 缓动函数的 GLSL 移植)、05/impulse.frag、05/cubicpulse.frag 等塑形函数库,它们与mod()一样属于"把输入约束到特定形态"的基础积木。
四、仓库源码实战:mod() 的六种典型用法
在 The Book of Shaders 的示例着色器中,mod()的身影遍布第 09~15 章。以下用法均来自仓库真实源码,可逐一打开对照学习。
1. 行交错偏移(砖墙平铺)
09/bricks.frag 用一行mod实现了砖墙的错缝平铺:
vec2 brickTile(vec2 _st, float _zoom){ _st *= _zoom; // Here is where the offset is happening _st.x += step(1., mod(_st.y,2.0)) * 0.5; return fract(_st); }mod(_st.y, 2.0)把纵向坐标折叠成周期为 2 的区间,step(1., ...)判断当前行是奇数行还是偶数行,奇数行把横向坐标平移0.5格,从而形成砖块交错效果。这正是"mod 周期折叠 + step 条件分支"的经典组合。
2. 单元格编号索引(Truchet 图案旋转)
09/truchet.frag 用mod为 2×2 网格的每个单元格分配唯一索引,再按索引旋转图案:
float index = 0.0; index += step(1., mod(_st.x,2.0)); index += step(1., mod(_st.y,2.0))*2.0;mod(_st.x, 2.0)与mod(_st.y, 2.0)分别折叠出 0/1 两个相位,组合成 0、1、2、3 四个索引,随后rotate2D()依据索引把单元格旋转 0°、90°、180°、-90°,生成无限延伸的 Truchet 纹样。
3. 位解码(易经卦象与 Ikeda 数字)
第 09 章的 09/iching-01.frag 用mod实现二进制位提取,把整数解码成六爻:
float remain = floor(mod(N,64.)); for(int i = 0; i < 6; i++){ b[i] = step(1.0,mod(remain,2.)); remain = ceil(remain/2.); }mod(remain, 2.0)每次取出当前最低位,配合ceil(remain/2.0)右移一位,循环 6 次即还原出卦象的 6 根爻线。同样的位解码套路出现在第 10 章 10/ikeda-digits.frag 中——它用mod(remain, 2.0)把 0~9 数字的 5×3 点阵位图逐位还原,再用floor(mod(n,10.))从随机数中选出数字。
4. 整数网格平铺与随机散点
10/2d-random-dots.frag 与砖块同款的交错技巧被用于随机圆点阵:
st *= 10.0; // Offset every other row st.x -= step(1., mod(st.y,2.0)) * 0.5; vec2 ipos = floor(st); // integer position vec2 fpos = fract(st); // float position这里mod(st.y, 2.0)折叠出行相位,floor取格子编号、fract取格内坐标——"整数位置 + 小数位置"的拆分正是格子空间的核心范式,而mod负责其中的周期交错。
5. 噪声与排列中的周期取模
第 11 章的 Simplex 噪声 11/3d-snoise.frag 用自实现mod289()把坐标折叠回有限周期格子:
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }这本质上就是x - y * floor(x/y)在y = 289时的特例。第 12 章 Voronoi 示例 12/cell.frag 的permute()同样依赖mod((34.0 * x + 1.0) * x, 289.0)完成伪随机排列,并配合mod(floor(P), 289.0)周期化格子坐标。可见mod是程序化噪声类算法中"保持计算有界、图案可周期重复"的关键。
6. 角度周期折叠(圆周动画与万花筒)
mod同样适用于极坐标角度。第 11 章 11/circleWave-noise.frag 用角度取模制造周期波纹:
float m = abs(mod(a+u_time*2.,3.14*2.)-3.14)/3.6;mod(a, 2π)把任意旋转后的角度折叠回[0, 2π),再减去 π 取绝对值得到以角度为周期、随时间流动的波瓣。第 15 章 15/texture-kaleidoscope.frag 用角度取模实现万花筒镜像:
float sides = 10.; float ma = mod(a, TWO_PI/sides); ma = abs(ma - PI/sides); st = r * vec2(cos(ma), sin(ma));mod(a, 2π/10)把圆周切成 10 个扇区,再折叠到单个扇区内,纹理即被对称地重复成 10 瓣花瓣——一个参数sides即可控制对称数量。
五、使用注意与常见误区
- 除数
y不能为 0。GLSL 中mod(x, 0.0)的结果未定义,实践中应保证y严格非零。 - 与 C 语言
%的区别。C 的%向零取整、结果符号随被除数;GLSL 的mod向下取整、结果符号随除数。写跨语言移植代码时务必留意。 - 与
fract的等价关系。fract(x) == mod(x, 1.0),当周期为 1 时可互换;但mod能表达任意周期,语义更明确。 - 动画中的时间取模。常见写法
mod(u_time, T)可把时间折叠进[0, T),实现循环动画;mod(u_time, 2π)则用于角度相位。 - 与
floor/ceil配合:mod负责折叠周期,floor负责取格子编号,二者几乎总是成对出现在空间平铺代码中(参考 09/bricks.frag 与 10/2d-random-dots.frag)。
六、动手练习建议
参考第 05 章 05/README.md 的练习思路,可围绕mod设计以下实验:
- 在 05/README.md 的交互图中将
y = mod(x, 0.5)的模数改为0.25、1.0、2.0,观察锯齿周期如何随y变化; - 打开 09/bricks.frag,把
mod(_st.y, 2.0)中的2.0改为其他值,观察砖缝错位节奏的变化; - 打开 15/texture-kaleidoscope.frag,调整
sides从 3 到 12,体会一个参数驱动对称性的威力; - 将
mod(u_time, 2π)接入 11/circleWave-noise.frag 的角度参数,让波瓣沿圆周周期旋转。
七、总结
mod()在 The Book of Shaders 中扮演着"周期折叠器"的角色:从第 05 章的一维锯齿波形,到第 09 章的砖块错缝与 Truchet 索引,再到第 11/12 章噪声算法中的mod289,直至第 15 章的万花筒对称——它把无限空间折叠进有限周期,把连续时间切成循环节拍。掌握x - y * floor(x/y)这一定义及其"结果符号随除数"的特性,你就掌握了 GLSL 程序化生成中最重要的复用与重复技巧。
- 相关词条:floor、fract、ceil
- 理论章节:Chapter 05: Shaping Functions
- 实战参考:09/bricks.frag、09/truchet.frag、10/2d-random-dots.frag、10/ikeda-digits.frag、11/3d-snoise.frag、12/cell.frag、15/texture-kaleidoscope.frag
【免费下载链接】thebookofshaders
Step-by-step guide through the abstract and complex universe of Fragment Shaders.
相关推荐
GLSL fract() 函数深度解析:The Book of Shaders 中的小数提取与图案平铺实战
GLSL fract 函数深度解析:The Book of Shaders 中的小数提取与图案平铺实战 本篇指南以《The Book of Shaders》开源
The Book of Shaders 术语详解:GLSL mod() 取模函数——从数学定义到平铺与对称实战
The Book of Shaders 术语详解:GLSL mod 取模函数——从数学定义到平铺与对称实战 导读 mod 是 GLSL 中最常用也最容易被低估的
The Book of Shaders 中的 pow():GLSL 幂函数与一维造型函数的基石
The Book of Shaders 中的 pow :GLSL 幂函数与一维造型函数的基石 导读 pow x, y 是 GLSL 中用于计算"以 x 为底、y
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考