news 2026/10/16 5:54:37

The Book of Shaders 术语详解:GLSL mod() 取模函数——从数学定义到平铺与对称实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
The Book of Shaders 术语详解:GLSL mod() 取模函数——从数学定义到平铺与对称实战

【免费下载链接】thebookofshaders

Step-by-step guide through the abstract and complex universe of Fragment Shaders.

项目地址:https://gitcode.com/gh_mirrors/th/thebookofshaders
点击查看免费下载

导读

mod()是 GLSL 中最常用也最容易被低估的内建函数之一:它在数学上等价于"除法取余数",但在着色器编程里,它更是构建网格平铺、奇偶行偏移、角度对称与程序化图案的核心工具。本文以 The Book of Shaders 仓库中的 mod 术语条目 为骨架,结合仓库内多个章节的真实.frag源码,系统讲解mod()的声明、参数、数学定义、与fract()/floor()/ceil()的关系,以及砖墙、Truchet 拼块、万花筒、噪声散列等实战用法。读完本文,你将理解为什么"把世界重复 N 份"只需要一行mod(),并能把这些技巧直接用到自己的着色器里。

一、术语条目:mod() 的声明、参数与定义

The Book of Shaders 仓库把 GLSL 内建函数整理为独立的术语表(glossary)目录,每个函数一个子目录,其中 glossary/mod/README.md 定义了mod(),并为每种语言提供了翻译版本(如 glossary/mod/README-vi.md 的越南语版)。其核心内容如下:

1.1 函数重载(Declaration)

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)

与fract()、floor()等函数只有单一参数不同,mod()接受两个参数,并支持两套重载规则:

  • 向量对向量:vecN mod(vecN x, vecN y),对每个分量分别执行取模;
  • 向量对标量:vecN mod(vecN x, float y),标量除数会广播到向量的每个分量。

例如mod(vec2(5.0, 7.0), 3.0)得到vec2(2.0, 1.0),因为分量按5 mod 3 = 2、7 mod 3 = 1独立计算。GLSL 内建函数"向量分量逐个运算 + 标量自动广播"这一约定,是许多技巧(如一次处理整块屏幕坐标)的基础。

1.2 参数含义(Parameters)

  • x:被除数(value to evaluate);
  • y:除数(value to obtain the modulo of),即取模的模数。

1.3 数学定义(Description)

mod()返回x除以y的余数,其精确定义为:

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

注意这里的floor()是关键。从该公式可以直接推断出两个容易被忽略的性质:

  1. 结果符号跟随除数y:因为floor(x/y)是向负无穷方向取整,当x为负数时,mod(-1.0, 2.0)得到-1.0 - 2.0 * floor(-0.5) = -1.0 - 2.0 * (-1.0) = 1.0,而不是 C 语言整数%那种向零截断得到的-1。这是 GLSL 取模与多数 C 系语言%运算符的重要差异。
  2. 除数不能为零:y = 0时公式中出现除零,GLSL 规范将其定义为未定义行为,实际编写着色器时应主动避免。

1.4 相关函数(See Also)

术语条目将mod()与以下内容关联,仓库中均有对应条目:

  • floor:向下取整,mod()公式的核心构件;
  • fract:取小数部分,本质上就是mod(x, 1.0);
  • ceil:向上取整;
  • 第 5 章:造型函数(Shaping Functions):mod()是造型函数工具箱里的重要一员。

fract与mod的关系尤其密切:glossary/fract/README.md 定义fract(x) = x - floor(x);而 第 9 章空间图案 中更是明确指出:fract()本质上是"对 1 取模"——即fract(x)等价于mod(x, 1.0),它返回浮点小数部分。这一等价关系把两个看似独立的函数统一了起来。

二、术语表页面的工作机制:多语言与查询参数

在深入用法之前,先了解这个术语条目在网站中是如何被服务的。glossary/index.php 是术语表入口,其工作流程为:

  1. 通过?lan=xx查询参数选择语言,若存在对应的README-xx.md则加载翻译版(例如?lan=vi加载 README-vi.md);
  2. 通过?search=mod这类参数定位具体函数,加载search参数指定目录下的README.md;
  3. 使用Parsedown库将 Markdown 渲染为 HTML 页面。

因此,仓库中 glossary/mod/README.md 与各语言版本(中文参考、越南语版 等)构成了一个"一函数一目录、多语言并行"的文档结构,这也是《The Book of Shaders》全书(README.md)一以贯之的写作方式。

三、实战一:用 mod() 判断奇偶行——砖墙平铺

mod()在图案生成中最经典的用法是"奇偶行判定"。第 9 章 09/README.md 专门用一个砖墙的例子来讲这件事:

要模拟砖墙,需要让每隔一行在 x 方向偏移半块砖。第一步就是判断当前行号是偶数还是奇数:

y = mod(x, 2.0); // y = mod(x, 2.0) < 1.0 ? 0. : 1. ; // y = step(1.0, mod(x, 2.0));

mod(x, 2.0)的结果要么落在[0, 1)(偶数行),要么落在[1, 2)(奇数行)。注释中提供了两种把结果变成 0/1 开关的方式:三元运算符< 1.0 ? 0. : 1.,以及更快的step(1.0, mod(x, 2.0))。书中特别强调:虽然难以确切知道每块显卡如何优化编译,但可以合理假设内建函数比自写表达式更快,因此"能用内建函数就用内建函数"。

再看仓库中实际可运行的 bricks.frag,第 14 行正是这个技巧的落地点:

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)得到当前行在 0~2 之间的余数,step(1., ...)把偶数行映射为 0、奇数行映射为 1,再乘以0.5的偏移量。偶数行偏移0.0 * 0.5 = 0.0,奇数行偏移1.0 * 0.5 = 0.5——半块砖的交错效果就这样完成了。该片段的渲染结果可见 bricks.png。

书中还给出了延伸练习:让偏移随时间动画化;让偶数行左移、奇数行右移;把行换成列;以及在 x、y 两个方向同时偏移(参考 marching_dots.frag)。核心都是同一个mod()奇偶判定。

四、实战二:用 mod() 给单元格编号——Truchet 拼块与 I Ching

比奇偶行更进一步,mod()可以为每个网格单元生成"编号",从而根据位置复用不同的设计元素。这就是第 9 章的 Truchet Tiles 案例(09/README.md):单一设计元素以四种旋转方式重复,就能拼出无穷多种复杂图案。

truchet.frag 的rotateTilePattern()函数展示了如何用两次mod()给 2×2 的四个格子编号:

// Give each cell an index number // according to its position 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、2、3 四个编号,随后根据编号对每个格子分别旋转 90°、-90° 或 180°(配合rotate2D与PI常量)。同一主题的 iching-01.frag 则把mod()用在了二进制解构上:

float remain = floor(mod(N, 64.)); for(int i = 0; i < 6; i++){ b[i] = 0.0; b[i] = step(1.0, mod(remain, 2.)); remain = ceil(remain/2.); }

这里mod(N, 64.)先把数值限制在 6 位二进制可表示的范围,然后循环mod(remain, 2.)逐位提取 0/1,从而把网格位置(ipos.x + ipos.y + t)编码成六爻卦象。可以看到,mod()既是"空间切分器",也是"数值解码器"。

五、实战三:用 mod() 制造角度对称——万花筒与圆波

mod()不仅作用于线性坐标,也常用于角度域,实现"扇形对称"。

15/texture-kaleidoscope.frag 是一个纹理万花筒:先把直角坐标转成极坐标(length(st)求半径、atan(st.y, st.x)求角度),再用mod()把角度折叠到单个扇形内:

// Repeat side according to angle float sides = 10.; float ma = mod(a, TWO_PI/sides); ma = abs(ma - PI/sides);

mod(a, TWO_PI/sides)把-π ~ π的角度范围切成sides个扇形,让所有角度折叠进同一份扇形区域;随后abs(ma - PI/sides)再做一次镜像折叠,实现双边对称。把折叠后的角度转回直角坐标并采样纹理u_tex0,便得到了"一片扇形纹理 + 10 次重复"的万花筒效果——整个对称性只来自一行mod()。

11/circleWave-noise.frag 使用了同族技巧,在极坐标上做循环动画:

float m = abs(mod(a + u_time * 2., 3.14 * 2.) - 3.14) / 3.6;

mod(a + u_time*2., TWO_PI)让角度随时间循环滚动并保持在[0, 2π),配合abs(... - 3.14)形成对称波形,再叠加噪声扰动。这也是"角度域取模"的典型应用。

六、实战四:mod() 在随机数与噪声散列中的角色

mod()还是许多经典散列(hash)与噪声算法的底层构件:

  • 10/2d-random-dots.frag 中的随机点阵同样依赖mod()做行偏移(st.x -= step(1., mod(st.y,2.0)) * 0.5;),与砖墙技巧一脉相承;
  • 12/cracks.frag 实现 Worley 细胞噪声时,用mod()构造了排列多项式(34x² + x) mod 289:
// Permutation polynomial: (34x^2 + x) mod 289 vec4 permute(vec4 x) { return mod((34.0 * x + 1.0) * x, 289.0); }
vec2 Pi = mod(floor(P), 289.0);

mod(..., 289.0)让散列坐标始终落在有限区间内,保证周期性边界下的确定性——细胞噪声、Perlin 噪声的实现几乎都离不开这种"取模回绕"。

七、实战五:mod() 在颜色转换中的应用

颜色变换也是mod()的常见战场。06/hsb-colorwheel.frag 中的 HSB→RGB 转换函数(源自 Iñigo Quiles)是这样使用mod()的:

vec3 hsb2rgb( in vec3 c ){ vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0,4.0,2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0 ); rgb = rgb*rgb*(3.0-2.0*rgb); return c.z * mix( vec3(1.0), rgb, c.y); }

mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0)把色相分量按六个扇区循环回绕,配合abs(... - 3.0)与clamp(...)生成 RGB 的六段渐变,实现色相环。色相本质上是"环绕 0~1 循环的量",mod()正是表达这种循环语义最自然的工具。

八、在造型函数中的应用:一维锯齿波

回到术语条目关联的第 5 章造型函数(05/README-ch.md)。该章用一个可交互的一维函数可视化面板让读者逐个体验 GLSL 造型函数,其中第一行就是mod():

y = mod(x, 0.5); // 返回 x 对 0.5 取模的值 //y = fract(x); // 仅仅返回数的小数部分 //y = ceil(x); // 向正无穷取整 //y = floor(x); // 向负无穷取整 //y = sign(x); // 提取 x 的正负号

在 0.0~1.0 区间内,mod(x, 0.5)会产生两条重复的锯齿波:值从 0 线性爬升到 0.5,然后跳回 0 再爬升。这就是mod()作为"造型函数"最直观的形态——它是把一维直线"折叠"成周期性重复波形的开关。注释中还并列展示了fract()、ceil()、floor()等函数的曲线形态,便于对照理解它们的差异。

九、使用建议与小结

综合术语条目与仓库源码,可以提炼出mod()的实用要点:

  1. 记住定义公式:mod(x, y) = x - y * floor(x/y),负数的行为与 C 语言%不同(结果符号跟随除数),需要负数取模时务必意识到这一点;
  2. fract(x) ≡ mod(x, 1.0):两者完全等价,可按可读性自由选择;
  3. 奇偶与编号:mod(coord, 2.0)配合step(1.0, ...)生成 0/1 开关,是砖墙、棋盘、点阵偏移的通用套路(参考 bricks.frag、truchet.frag);
  4. 角度对称:mod(angle, TWO_PI/N)可以把角度折叠进单个扇形,再配合abs做镜像,快速实现万花筒与多边对称(参考 texture-kaleidoscope.frag);
  5. 数值回绕与散列:mod(v, N)让数值周期回绕,是噪声散列(cracks.frag)、HSB 色相环(hsb-colorwheel.frag)等算法的共同底层;
  6. 避免除零:除数y为 0 时结果未定义,代码中应保证除数恒非零。

mod()看起来只是一个"取余数"的小函数,但在 The Book of Shaders 的实践体系中,它串联起了空间平铺(第 9 章)、随机与噪声(第 10~12 章)、纹理变形(第 15 章)和颜色运算(第 6 章)等多个核心主题。理解了mod()的"重复与折叠"语义,就等于掌握了程序化图案生成中最基础也最强大的一块积木。

【免费下载链接】thebookofshaders

Step-by-step guide through the abstract and complex universe of Fragment Shaders.

项目地址:https://gitcode.com/gh_mirrors/th/thebookofshaders
点击查看免费下载
上一篇:为什么选择Spring Security OAuth:与其他框架的终极对比分析
下一篇:Pagination.js完整使用指南:打造高效网页分页体验终极教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 5:54:35

TL494中文资料应用电路详解:从引脚定义到单端推挽拓扑落地

简介&#xff1a;TL494中文资料及应用电路文档面向开关电源设计初学者与硬件工程师&#xff0c;系统梳理这款固定频率脉宽调制控制器的核心知识。内容涵盖芯片概述、主要特性、工作原理、应用电路、引脚图与封装、极限参数等模块&#xff0c;帮助读者理解单端正激、半桥、全桥等…

作者头像 李华
网站建设 2026/10/16 5:54:31

Akamai Valkey托管数据库:企业AI实时内存数据层实战指南

1. 为什么“实时内存”成了企业 AI 落地的硬门槛做过 AI 应用开发的人都有一个共同体会&#xff1a;模型本身的能力已经不是瓶颈了&#xff0c;真正卡住项目上线的&#xff0c;往往是数据层。尤其是当你的 AI Agent 需要频繁读写会话状态、用户画像、向量检索缓存、任务队列的时…

作者头像 李华
网站建设 2026/10/16 5:51:52

AGI刹车与人形机器人提速:量产拐点背后的逻辑与判断

这期想聊一个特别拧巴的现象&#xff1a;一边是AGI前夜的各种“刹车”&#xff0c;大模型厂商主动收缩产品边界、投资人在巨额算力账单前开始算账&#xff0c;连开源社区的热度也比去年同期冷静了不少&#xff1b;另一边却是人形机器人量产消息密集得让人应接不暇&#xff0c;从…

作者头像 李华
网站建设 2026/10/16 5:50:18

蜗轮蜗杆减速器课程设计全流程:从参数计算到图纸绘制

简介&#xff1a;这份机械设计课程设计说明书面向机械类专业的本科生与课程设计指导教师&#xff0c;围绕单级蜗轮蜗杆减速器这一典型课题&#xff0c;提供从参数选择到结构设计的完整设计流程参考。资源包内含1个doc文档&#xff0c;大小约53KB&#xff0c;内容涵盖任务设计书…

作者头像 李华
网站建设 2026/10/16 5:50:15

汇川默纳克11kW底座原理图:机电协同设计与现场调试关键依据

简介&#xff1a;本资源为汇川默纳克11KW底座专用原理图PDF&#xff0c;面向工业自动化维修工程师、变频驱动系统技术人员及电路板故障排查从业者&#xff0c;解决电机驱动板级维修中缺乏权威参考图纸、难以定位AFE前端异常与IGBT驱动异常等核心问题。文件共1个PDF&#xff08;…

作者头像 李华