资讯详情 着色器图案:The Book of Shaders 空间平铺、偏移与 Truchet 瓷砖实战指南
📅 2026/10/15 20:49:57
【免费下载链接】thebookofshadersStep-by-step guide through the abstract and complex universe of Fragment Shaders.项目地址https://gitcode.com/gh_mirrors/th/thebookofshaders点击查看免费下载在 fragment shader 中程序按像素逐个执行因此无论把同一个图形重复多少次计算总量都保持不变——这让着色器天然适合生成平铺tile图案。本章是《The Book of Shaders》算法绘图部分的收官章节讲解如何通过对归一化空间坐标进行乘法缩放与fract()取模把 0.01.0 之间画出的单个形状重复成无限网格再结合矩阵变换、奇偶行偏移和 Truchet 瓷砖规则生成砖墙、菱格、几何拼接等复杂图案。读完本文你将掌握空间平铺的核心公式、奇偶单元判定的两种写法以及如何用最小可复用元素构建属于自己的程序化图案规则。核心原理fract()与空间坐标平铺shader 程序逐像素执行重复多少次图形都不会增加计算量这正是 fragment shader 特别适合平铺图案的原因。本章的策略与前几章一致乘以空间坐标0.0 与 1.0 之间让 0.01.0 之间画出的形状沿画布重复形成网格。先回顾fract()函数。它返回一个数的分数部分本质上是除以 1 的余数等价于mod(x, 1.0)。也就是说fract()返回浮点小数点之后的数字。归一化坐标变量st已经处于 0.01.0 之间所以直接对它取模没有意义void main(){ vec2 st gl_FragCoord.xy/u_resolution; vec3 color vec3(0.0); st fract(st); color vec3(st,0.0); gl_FragColor vec4(color,1.0); }但若把归一化坐标系放大——比如放大 3 倍——就会得到三组 01 之间的线性插值序列第一组在 01 之间第二组对应 12 之间的浮点数第三组对应 23 之间的浮点数。仓库中的 grid-making.frag 正是这一思路的最小实现void main() { vec2 st gl_FragCoord.xy/u_resolution; vec3 color vec3(0.0); st * 3.0; // 将空间放大 3 倍 st fract(st); // 环绕到 1.0 以内 // 现在得到 9 个 0-1 的子空间 color vec3(st,0.0); //color vec3(circle(st,0.5)); // 第 27 行在每个子空间画圆 gl_FragColor vec4(color,1.0); }grid-making.frag 第 2122 行是全部诀窍先st * 3.0把坐标范围扩张到 03再用fract(st)折叠回 01。取消第 27 行注释即可在每个子空间画出圆形由于 x、y 被等比例放大空间纵横比不变图形形状符合预期。仓库的 09/notes.md 中记录了读者对这一过程的数值验证以st取值 0.01.0 为例st * 3在第二、三段会超过 1而fract(st * 3)始终保持在 01 之间从而保证每个子空间内仍是可用的归一化坐标系st 0.30 | st * 3 0.90 | fract( st * 3 ) 0.90 // 第一段 st 0.50 | st * 3 1.50 | fract( st * 3 ) 0.50 // 第二段 st 0.80 | st * 3 2.40 | fract( st * 3 ) 0.40 // 第三段深入理解的练习改变缩放倍数把空间乘以不同数字尝试浮点值以及 x 与 y 使用不同系数这会产生非正方形网格封装可复用函数把缩放 取模的平铺技巧提炼为独立函数例如 grid.frag 中的tile()vec2 tile(vec2 _st, float _zoom){ _st * _zoom; return fract(_st); }该函数在 grid.frag、checks.frag、truchet.frag、dots.frag、cross.frag、diamondtiles.frag 等多处重复出现是整章最核心的公共工具3×3 井字棋把空间分成 3 行 3 列找出当前线程位于第几行第几列并据此切换显示的图形——这正是下一节单元定位思想的雏形。在图案内部应用矩阵变换每个细分单元都是归一化坐标系的一个缩小版本因此可以对单元内部的空间施加矩阵变换实现平移、旋转和缩放。仓库示例 checks.frag 演示了完整链路先用tile()把空间分成 4 份再用旋转矩阵把每个单元旋转 45 度最后画出方形vec2 rotate2D(vec2 _st, float _angle){ _st - 0.5; _st mat2(cos(_angle),-sin(_angle), sin(_angle),cos(_angle)) * _st; _st 0.5; return _st; } void main(void){ vec2 st gl_FragCoord.xy/u_resolution.xy; vec3 color vec3(0.0); // 把空间分成 4 份 st tile(st,4.); // 用矩阵把空间旋转 45 度 st rotate2D(st,PI*0.25); // 画一个正方形 color vec3(box(st,vec2(0.7),0.01)); gl_FragColor vec4(color,1.0); }注意rotate2D()中先平移-0.5使旋转围绕单元中心进行旋转完再移回0.5——这是 08 章矩阵 内容的直接复用。练习想三种方式让该图案动起来动画颜色、动画形状、动画运动组合不同形状重建更复杂的图案例如 diamondtiles.frag 中通过偏移 旋转 多个 box 叠加构造出菱形平铺box(offsetSt,...) - box(st,...) 2.*box(st,...)三个方框的加减运算直接决定了最终图案的黑白层次尝试叠加多层不同密度的图案层如 7 格层与 3 格层混用合成自己的苏格兰格子呢Scottish Tartan图案——dots5.frag灵感来自 Nina Warmerdam 的 IMPRINT 项目就是双层网格叠加、再混色得到复杂视觉的典型例子。偏移图案用奇偶判定实现砖墙假如要模拟一面砖墙观察墙面会发现每隔一行x 方向就有半块砖的偏移。第一步是判断当前线程所在行是奇数行还是偶数行据此决定该行是否需要偏移 x。为此使用mod(x, 2.0)再看结果是否小于1.0y 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)是否小于 1.0第二行也可以用功能相同但更快的step()函数第三行。为什么不推荐三元虽然难以确知每块显卡如何优化和编译代码但可以稳妥地假设内置函数比非内置写法更快——只要能用内置函数就用它。09/notes.md 还给出了另外两种等价替代y floor( mod( x, 2.0 ) );只需从内存加载一个值更进一步把mod()结果强转为int再转回floatfloat( int( mod( x, 2.0 ) ) )也能丢掉小数部分等效于floor()但要注意结果需重新转回float。三元运算符本质上是隐藏的if程序被迫分支并求值 if/else 两个分支会拖慢执行。有了奇数判定公式就可以对奇数行施加半单位偏移给砖块加上效果。bricks.frag 第 14 行正是用来侦测奇数行、并在 x 上给它们半单位偏移的地方vec2 brickTile(vec2 _st, float _zoom){ _st * _zoom; // 偏移发生的地方 _st.x step(1., mod(_st.y,2.0)) * 0.5; return fract(_st); }对偶数行step(1., mod(_st.y,2.0))结果为0.00.0 × 0.5 0.0即无偏移对奇数行函数结果为1.01.0 × 0.5 0.5坐标系 x 轴整体右移 0.5。现代砖比例与调试继续在 bricks.frag 中尝试取消第 32 行注释——st / vec2(2.15,0.65)/1.5;会拉伸坐标系纵横比模拟现代砖现代公制砖规格 215mm × 102.5mm × 65mm出处见代码内注释引用的砖尺寸参考页的长宽比取消第 40 行注释——color vec3(st,0.0);把空间坐标映射到红绿两色可以直观看到偏移后的坐标系形态主函数中st brickTile(st,5.0);以 5 倍缩放平铺box()用smoothstep画出带 1e-4 抗锯齿边界的矩形单元。动画练习让偏移量随时间变化把0.5换成u_time驱动的值做动画让偶数行左移、奇数行右移把同样的效果套用到列上在 x 与 y 两个轴同时施加偏移——marching_dots.frag 给出了完整实现movingTiles()根据时间相位fract(time)0.5切换偏移方向x 轴与 y 轴交替行/列奇偶判定用fract(_st.y * 0.5) 0.5从而让整片圆点网格如波浪般交错游走。Truchet 瓷砖一个元素四种姿态学会了判断单元处于奇数/偶数行或列之后就可以按位置复用同一个设计元素。Truchet 瓷砖 正是典型单个设计元素对角线黑白三角可以四种不同方式呈现跨瓷砖改变图案朝向就能构造出无限多种复杂设计关键在于rotateTilePattern()函数它把空间细分成 4 个单元并给每个单元分配一个旋转角truchet.fragvec2 rotateTilePattern(vec2 _st){ // 将坐标系放大 2x2 _st * 2.0; // 根据位置给每个单元编号 float index 0.0; index step(1., mod(_st.x,2.0)); index step(1., mod(_st.y,2.0))*2.0; // | // 2 | 3 // | //-------------- // | // 0 | 1 // | // 让每个单元回到 0.0 - 1.0 _st fract(_st); // 根据编号旋转对应单元 if(index 1.0){ _st rotate2D(_st,PI*0.5); // 单元 1 旋转 90 度 } else if(index 2.0){ _st rotate2D(_st,PI*-0.5); // 单元 2 旋转 -90 度 } else if(index 3.0){ _st rotate2D(_st,PI); // 单元 3 旋转 180 度 } return _st; }编号方案值得细读step(1., mod(_st.x,2.0))对右半区返回 1、左半区返回 0贡献 index 的低位step(1., mod(_st.y,2.0))*2.0对上半区返回 2、下半区返回 0贡献高位。于是四个象限获得 0、1、2、3 四个编号再配合fract()折叠与if/else旋转主函数中step(st.x,st.y)画出的黑白三角形便自动获得四种朝向void main (void) { vec2 st gl_FragCoord.xy/u_resolution.xy; st tile(st,3.0); st rotateTilePattern(st); // 更复杂的组合原文 69-72 行 // st tile(st,2.0); // st rotate2D(st,-PI*u_time*0.25); // st rotateTilePattern(st*2.); // st rotate2D(st,PI*u_time*0.25); // step(st.x,st.y) 生成黑白三角形 // 你也可以换成任意想要的图形。 gl_FragColor vec4(vec3(step(st.x,st.y)),1.0); }练习注释、取消注释、复制第 6972 行叠加缩放、旋转与时间驱动组合出新设计——例如rotate2D(st, -PI*u_time*0.25)让整片图案随时间缓慢旋转把黑白三角替换为其他元素半圆、旋转的方形或线条编写元素按自身位置旋转的其他图案rotatedtiles.frag 用同样的四象限 index 机制但把三角形替换为带平滑边缘的几何三角形制作一个按元素位置改变其他属性颜色、大小等的图案想想不一定是图案、但能应用本节原理的场景——iching-01.frag 把按位置切换形态的思想用于绘制易经卦象hex()函数把每个单元纵向切成 6 段用step(1.0, mod(remain,2.0))按索引号 N 的二进制位逐段决定断爻/连爻再用ipos.x ipos.y t把网格坐标编码进卦序实现整片网格滚动演绎六十四卦。此外仓库还提供了两类可替换的平铺变体思路mirrortiles.frag 用if (fract(_st.y * 0.5) 0.5) _st.y 1.0-_st.y;实现镜像平铺生成连续起伏的波形纹理zigzag.frag 在镜像基础上叠加sin驱动的锯齿边界进一步扩展了单元内变换的想象空间。制定自己的规则从最小可复用元素出发制作程序化图案是一种寻找最小可复用元素的心智练习。这种实践由来已久——人类长久以来用网格与图案装饰织物、地板和器物边缘从古希腊的蜿蜒回纹meander到中国的窗棂格栅重复与变化带来的愉悦始终激发着想象力。从阿拉伯几何纹样到绚丽的非洲织物设计图案世界浩瀚无边。而着色器的优势在于你只需画出一个单元网格便会替你完成全部重复你真正要设计的是这个最小元素以及它随位置变化的规则——这正是本章tile()、rotateTilePattern()、brickTile()三个函数的共同内核。本章同时为算法绘图部分画上句号。接下来的章节将把熵随机性引入 shader进而产出真正具有生成式美感的图案设计。本章源码速查主题示例文件关键函数/行号空间缩放平铺09/grid-making.fragst * 3.0; st fract(st);21-22 行可复用平铺函数09/grid.fragtile(_st, _zoom)单元内矩阵变换09/checks.fragrotate2D()tile()菱形平铺09/diamondtiles.fragoffset()rotate2D() 多层 box奇偶行偏移砖墙09/bricks.fragstep(1., mod(_st.y,2.0)) * 0.514 行双向交错偏移动画09/marching_dots.fragmovingTiles()Truchet 瓷砖09/truchet.fragrotateTilePattern()组合行 69-72位置驱动卦象09/iching-01.fraghex()按索引位分解爻象镜像平铺09/mirrortiles.fragmirrorTile()锯齿镜像09/zigzag.fragmirrorTile(st*vec2(1.,2.),5.)需要回顾基础函数时可查阅仓库的 glossary/fract/README.md、glossary/mod/README.md 与 glossary/step/README.md它们给出了各函数的声明、参数与x - y * floor(x/y)等精确计算公式。赞分享【免费下载链接】thebookofshadersStep-by-step guide through the abstract and complex universe of Fragment Shaders.项目地址https://gitcode.com/gh_mirrors/th/thebookofshaders点击查看免费下载相关推荐The Book of Shaders 第 9 章精读GLSL 空间平铺Tiling与程序化重复图案实战The Book of Shaders 第 9 章精读GLSL 空间平铺Tiling与程序化重复图案实战 本文精读 The Book of ShadersThe Book of Shaders 精讲用 fract() 与坐标变换在片段着色器中生成平铺图案PatternsThe Book of Shaders 精讲用 fract 与坐标变换在片段着色器中生成平铺图案Patterns 《The Book of ShadersThe Book of Shaders 第 9 章解读用 fract() 与空间平铺构建程序化图案The Book of Shaders 第 9 章解读用 fract 与空间平铺构建程序化图案 本篇技术指南围绕开源教程 The Book of Shader上一篇Chunker打破Minecraft平台壁垒的终极世界转换指南下一篇情感语音革命解密EmotiVoice如何重构2000音色TTS技术范式创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考