06 floor、ceil、fract 与重复图案
学完本章能做什么
你将能把一张平面分成多个重复单元,同时得到“当前在第几个单元”和“当前位于单元内部哪里”两种信息,并用它们制作棋盘格及隔行错位图案。
先看现象
示例默认模式显示很多小块相同的渐变。改变“每轴重复次数”,小块数量随之变化。切换到棋盘格时,相邻单元的颜色交替;切换到阶梯模式时,可直接看到 floor 与 ceil 产生的离散数值;“隔行错位”则让奇数行的局部图案相对偶数行移动半个单元。
1. 四个函数的基本行为
x |
floor(x) |
ceil(x) |
fract(x) |
|---|---|---|---|
2.3 |
2.0 |
3.0 |
0.3 |
2.0 |
2.0 |
2.0 |
0.0 |
-1.2 |
-2.0 |
-1.0 |
0.8 |
floor朝负无穷方向取整数;负数时尤其不能理解成“直接去掉小数”。ceil朝正无穷方向取整数。fract(x) = x - floor(x),留下从0.0到小于1.0的小数部分。因此fract(-1.2)也是0.8。
mod(x, y) 则按周期 y 求余,GLSL 中可理解为 x - y * floor(x / y)。例如 mod(5.0, 2.0) 为 1.0;mod(-1.2, 1.0) 为 0.8。它与某些语言对负数使用的 % 余数规则不同;为减少混淆,初学时先用正的周期值。
这些函数有向量版本,作用于 vec2 时会分别处理 x、y 两个分量。
2. 先放大 UV,再拆成单元编号和局部坐标
本章最重要的三行代码是:
vec2 grid = vUv * uRepeat;
vec2 cell = floor( grid );
vec2 localUv = fract( grid );
假设 uRepeat = 5.0,某片元 vUv = vec2(0.63, 0.22):
grid = (3.15, 1.10)
cell = (3.00, 1.00) 第 3 列、第 1 行(从 0 开始)
localUv = (0.15, 0.10) 当前单元内部的位置
grid 跨过多少个整数区间,就有多少个单元。floor 把连续坐标转换为单元编号;fract 每跨过一个整数就重新从 0 开始。每个单元都得到自己的 0.0~1.0 局部 UV。示例中的 vec3(localUv, 0.25) 因而在每个单元重画相同渐变。
注意运算顺序:fract(vUv * uRepeat) 才会在放大后重复。fract(vUv) * uRepeat 对本例的 0.0~1.0 UV 只是放大一次,结果不会被重新压回每个单元的 0.0~1.0 范围。
3. 用单元编号制作棋盘格
float parity = mod( cell.x + cell.y, 2.0 );
vec3 color = mix( darkColor, lightColor, parity );
本例的 cell.x、cell.y 是非负整数值,但类型仍是 float。两者之和为偶数时,除以 2 的余数是 0;为奇数时余数是 1。横向或纵向走到下一个单元,和的奇偶性翻转,所以颜色交替。这里的 mod 选择单元颜色,fract 则负责单元内图案,两者可以同时使用。
4. floor 与 ceil 的阶梯
示例的阶梯模式把取整结果显示为颜色:
float lower = floor( grid.x ) / uRepeat;
float upper = ceil( grid.x ) / uRepeat;
vec3 color = vec3( lower, upper, localUv.x );
红通道在一个单元内保持低位常量,绿通道显示上界;蓝通道则从 0 逐渐增加,在下一个单元重新开始。正好处在整数边界时,floor(grid.x) 与 ceil(grid.x) 相同,所以两通道在边界点可能暂时相等。这个模式帮助分辨“单元编号”和“单元内位置”。
5. 利用编号让相邻行不一样
先用行号判断奇偶,再给奇数行的 x 坐标加半个单元:
float oddRow = mod( cell.y, 2.0 );
vec2 shifted = vec2( fract( grid.x + 0.5 * oddRow ), localUv.y );
偶数行 oddRow = 0.0,x 局部坐标保持原样;奇数行 oddRow = 1.0,x 局部坐标整体偏移 0.5 后再经 fract 折回 0.0~1.0。这样仍是重复图案,却不再每行完全对齐。以后制作砖墙、错位圆点或周期动画时可以沿用这个思路。
6. 重复图案的边界与比例
fract 在整数边界突然从接近 1 回到 0,所以渐变单元之间会出现可见接缝。这是重复坐标的预期行为;若需要连续纹理,应设计两侧颜色一致,或用平滑遮罩处理接缝。
示例使用正方形平面,两个轴采用相同重复次数。如果把同样的 UV 画在非正方形区域,屏幕上的单元可能变成长方形。第 7 章会详细解释如何根据实际显示比例修正圆形、图案和图片。重复次数过高时还可能出现闪烁或摩尔纹;增大网格分段不能解决片元采样问题。
常见问题
- **只看到一个被拉大的图案:**检查是否写成了
fract(vUv) * uRepeat;需要先乘再fract。 - **相邻单元没有不同颜色:**你可能只用了
localUv,忘记使用cell = floor(grid)。 - 负坐标时的编号出乎意料:
floor向负无穷取整,fract对负数仍返回非负小数部分。 - **格子不是正方形:**检查承载图案的区域比例;下一章会进行比例修正。
- **重复次数变大后边缘抖动:**检查单元在屏幕上是否只占少量像素;这是采样问题,单纯增加平面分段通常无效。
本章小结
floor(vUv * repeat) 告诉我们位于哪个单元,fract(vUv * repeat) 告诉我们位于单元内部哪里;mod 可以根据单元编号制造交替规律。这个“编号 + 局部坐标”的拆分,是许多程序化图案的基础。