中EN
精修课概念页

程序化图案:画一格,铺满一墙

Procedural Patterns

更新于 2026-10-15 约 18 分钟平铺fractTruchetGLSL程序化

为什么着色器擅长重复

片元着色器对每个像素只算一次。一块画布上画 1 个圆还是 100 个圆,每个像素要做的计算几乎一样多。所以重复的图案特别适合交给着色器:重复的次数几乎不影响开销。

《10 PRINT》这本书里说,网格给人的直觉和创造提供了一个框架,也留下了打破它的余地;在混沌的自然里,图案许诺了一种秩序。从陶器上的纹样到罗马浴场的马赛克,人们一直在用网格装饰生活。

fract:把空间切成格子

fract(x) 返回 x 的小数部分,相当于 mod(x, 1.0)。st 本来就在 0 到 1 之间,直接取 fract 什么也不会发生。但如果先把 st 乘以 3,坐标就会从 0 走到 3,再取 fract,就得到三段从 0 到 1 的重复。

01/32/31xfract(3.0 * x)每一格都是一个新的 0~1
左:把 x 放大 3 倍再取小数部分,一条斜线变成三段锯齿,每一段都是一个新的 0~1。右:横纵两个方向都这样做,画布就被切成 3 × 3 个格子,每一格都能画同一个形状。

每一格里的坐标都是一个缩小版的 st,所以前几课画形状的代码可以原封不动地搬进来:

放大,取 fract,每格画一个圆
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

背景的红绿渐变就是每一格里的坐标:每格都从左下角的 0 走到右上角的 1。

floor:知道自己在第几格

fract 告诉你「在格子里的哪里」,floor 告诉你「在第几格」。两者是一对:st * 3.0 = floor(st * 3.0) + fract(st * 3.0)。

有了格子的编号,就能让不同的格子画不同的东西。书里的练习是画一盘井字棋:

井字棋:按格子编号决定画什么
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

board() 按格子的列号和行号返回这一格该画 ×、○ 还是空着。把鼠标移进来,所在的格子会亮一点:鼠标的坐标也用 floor 算出了格子编号。

在每一格里变换坐标

每一格都是一个小小的坐标系,上一课的平移、旋转、缩放可以直接用在格子里。先用 fract(st) - 0.5 把每格的原点挪到格子中心,再乘旋转矩阵,格子里的形状就会原地自转。再配合棋盘格的编号,让相邻的格子朝相反的方向转:

每一格各转各的0.0s
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

checker = mod(id.x + id.y, 2.0) 在相邻格子之间 0、1 交替,正好拿来决定旋转方向和底色。

错位:砌一面砖墙

看一面砖墙:每隔一行,砖就往旁边错开半块。要做到这一点,先得知道当前像素在奇数行还是偶数行:对行号取 mod(row, 2.0),结果不是 0 就是 1。

第 0 行 · 偶数行,不动第 1 行 · 奇数行,x + 0.5第 2 行 · 偶数行,不动第 3 行 · 奇数行,x + 0.5
偶数行不动,奇数行的 x 加 0.5,也就是往右挪半块砖。

判断奇偶可以写三元运算 mod(row, 2.0) < 1.0 ? 0.0 : 1.0,也可以写 step(1.0, mod(row, 2.0))。书里推荐后者:虽然很难知道每块显卡具体怎么编译优化,但可以放心假设内建函数比自己写的逻辑更快。能用内建函数时就用。

会滑动的砖墙0.0s
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

格子先被拉成 2:1 的长条,像现代的砖。奇数行和偶数行朝相反的方向滑。

Truchet:一块砖,四个朝向

知道了格子的位置,就能让同一个元素按位置摆出不同的朝向。Truchet 砖就是这个思路:一块被对角线切开的方砖,转 0°、90°、180°、270° 就有四种摆法。按一定规则把它们拼在一起,能组合出无穷多种复杂的图案。

0°90°180°270°按位置挑朝向
左边是同一块砖的四个朝向,右边是按位置挑选朝向后拼出的一小片图案。

下面的例子把每个格子再切成 2 × 2 个小格,按小格的位置决定转几次 90°。拖动「样式」滑块,把三角换成两段四分之一圆弧,图案会变成一条条连贯的曲线:

Truchet 拼贴
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

规则藏在 k 那两行:先按小格位置定一个基础朝向,再按大格位置错开。改一改这两行,图案就完全不一样。

在游戏里怎么用

取舍与常见的坑

小结与练习

  • fract(st * n) 把画布切成 n × n 个格子,每一格都是一个新的 0~1 坐标系;floor(st * n) 给出格子的编号。
  • 在格子里可以照常画形状,也可以平移、旋转、缩放;按编号的奇偶可以做错位和棋盘格。
  • Truchet 砖用同一个元素的不同朝向拼出复杂的图案。

练习:设计一块自己的地砖:用这几课学到的形状画一个最小单元,然后用格子编号决定它的颜色和朝向,铺满整个画布。

  1. The Book of Shaders: Patterns
  2. thebookofshaders.com/09

相关页面

图形学二维 SDF 形状2D SDF Shapes在片元着色器里画形,不是发三角形图元,而是对每个像素问「我在形的里面还是外面」。矩形用 step/smoothstep 做轴对齐边界;圆与更复杂形用距离场 (distance field)——到特征点的距离再阈值。这是 2D SDF 程序化造型的入门路径。图形学着色器二维矩阵变换2D Matrix Transforms in Shaders在片元里「移动形状」其实是移动坐标系:平移加向量;旋转与缩放用 mat2 乘到坐标上。绕画布中心变换时,要先把空间挪到原点、变换、再挪回。矩阵乘法顺序有关;同一套矩阵也可拿来做 YUV↔RGB 等颜色空间线性变换。图形学着色器伪随机Shader Pseudorandom片元着色器里没有真正的「每次调用都不同」的随机:rand(x) 必须是确定性伪随机——同一输入永远同一输出。常用手法是 fract(sin(x)大数);二维则用 dot 把 vec2 压成标量再喂给同一套路。把空间 floor 成格子后,格子共享一个随机值,就能在秩序与混沌之间做生成设计。图形学塑形函数Shaping Functions塑形函数把归一化到 [0,1] 的一维输入弯成可控制的曲线,是片元着色器里混合颜色、画边、做动画缓动的「基本功」。GLSL 的 pow/step/smoothstep/sin 等多为硬件加速;掌握一维再扩展到二维空间。图形学噪声函数Noise FunctionsKen Perlin 为电影《电子世界争霸战》(Tron) 设计的噪声,解决「看起来随机却不像电视雪花」的程序纹理问题。入门路径是值噪声 (value noise):格子角点取伪随机,格内用平滑曲线插值;进阶有梯度噪声 (gradient noise)、五次插值改进版,以及用单纯形网格降复杂度的 Simplex Noise。图形学细胞噪声Cellular NoiseSteven Worley(1996)《A Cellular Texture Basis Function》——书中注明约在 Perlin 原始噪声发表十六年之后、其 Simplex Noise(2001)五年之前——把程序纹理建立在「到最近特征点的距离」上,即细胞噪声 / Worley 噪声。GPU 上用格子 + 邻域搜索代替「对全体点求最小距离」;再多存最近特征点身份即可得到 Voronoi 图。后续有 2×2 邻域加速、精确边界与「voro-noise」等变体。

被这些页面引用3 篇

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原