第 4 课:形状与图案(SDF)
一个数决定一个形状
按钮底板、选中框、图标轮廓和程序化花纹,经常需要随着大小和颜色变化而不换图片。带符号距离场把每个位置映射为一个数 d:d < 0 在形状内,d = 0 在边界,d > 0 在形状外。片元着色器只要知道当前片元的 vUv,就能给每个片元算出 d,再决定颜色。
本课沿用第 3 课的 UV:平面左下 (0,0),右上 (1,1)。p = vUv - 0.5 将原点移到画面中心。本课预览区域为正方形,因此 UV 的 x/y 相同长度在屏幕上占相同像素。若图案贴到长方形物体上,要按物体表面的实际宽高调整距离;若图案覆盖全屏宽画布,可按第 3 课的方法修正画布比例。
从距离到可见颜色
正方形的近似带符号距离可写:
vec2 p = vUv - vec2(0.5);
float d = max(abs(p.x), abs(p.y)) - 0.28;
float fill = 1.0 - smoothstep(-0.004, 0.004, d);
vec3 color = mix(background, foreground, fill);
max(abs(x),abs(y)) 是到中心的切比雪夫距离。边界是 d=0,所以半宽为 0.28。smoothstep 把边界附近的数值变成 0–1 遮罩;过渡区 0.008 UV 在 560 像素的正方形预览中约为 4.5 像素。它是固定 UV 宽度的柔边,不是真正按像素自动调整的抗锯齿。之后可用屏幕导数 fwidth(d) 得到更稳定的像素级边宽。
若需要外描边,可在 d 接近 0 的一小段范围内单独着色,例如 1.0 - smoothstep(0.01,0.02,abs(d))。改变 d 的常数项会整体扩张或收缩形状,这比手动改每个顶点容易。
形状的组合公式
圆角矩形先把坐标折到第一象限:q = abs(p) - (halfSize - radius)。length(max(q,0.0)) 算角外的圆弧距离,min(max(q.x,q.y),0.0) 补上内侧的负距离,最后减去圆角半径。半径增大,角变圆;如果半径大于半尺寸,几何意义会改变,因此普通圆角矩形应限制它。
两个距离场可以组合:min(dA,dB) 表示并集,max(dA,dB) 表示交集,max(dA,-dB) 表示从 A 中挖去 B。第 3 题用横竖两条矩形的 min 组成十字,第 8 题用两个圆和一个倒三角组成爱心。这里的正负号关系决定内部和外部:如果组合后颜色反了,先把 d 输出为灰度或临时画出 d<0 的区域,再检查 min/max。
第 7 题使用三条边的有向半平面。每条边在内部返回负值,max 要求三条约束都成立,因此得到三角形。这个函数的零等值线与内部判断正确,但在顶点外侧的数值不是精确欧氏最短距离。需要严格等宽的远距离描边或形态运算时,应换成精确多边形 SDF;本课只用它判断填充和近边缘柔化。
重复、曲线与鼠标选择
fract(vUv*5.0) 把整张 UV 重新折回每个格子的 0–1 范围,局部再减去 0.5,即可在 5×5 格子中心放圆点。floor(vUv*5.0) 给出格子的行列编号,能让半径或颜色按行列变化。fract 用于格内位置,floor 用于格子身份;把二者搞反,往往会得到只有一颗圆点或每个格子完全相同的结果。
正弦波线用 abs(vUv.y - curveY) 表示当前片元与曲线的竖直差值。它足够绘制细线,但当曲线倾斜很大时,并不等于到曲线的最短距离。若需要沿曲线严格等宽的描边,要计算真正的最近点或使用几何线段。
第 9 题在 JS 中把指针 X 坐标分成八段,传给 uShape。这是一个 uniform,因此一次 draw call 内所有片元选择同一个图案。少量静态分支便于初学者读懂;如果日后有几十种复杂图案,分支、着色器体积和移动端性能都值得测量,也可以拆成多个材质或预先生成图案纹理。
数据如何到达屏幕
main.js 建立一个平面。Three.js 把每顶点的 position 与 uv attribute 送入顶点 Shader;顶点 Shader 负责位置变换,并把 uv 放进 vUv。光栅化阶段在平面内部插值 vUv。片元 Shader 对每个片元计算形状距离、遮罩和颜色。只有第 9 题需要 JS 根据指针更新 uShape;前八题的公式完全写在片元 Shader 中。颜色最终由 Three.js 转到输出颜色空间。
常规 UI 如果只要一个静态圆角矩形,CSS 往往更简单;需要把图案贴到 3D 物体上、连续变形、和其他材质效果组合时,SDF 更灵活。避免在渲染循环里新建材质或几何体;本课只更新 uniform,公共运行时在页面离开时释放资源。
常见错误与验证
- 图形不居中:确认减的是
vec2(0.5),而不是只有 x 或 y。 - 形状变椭圆:检查预览区域或物体表面是否为正方形,再决定用画布比例还是物体比例修正。
- 并集显示成重叠区:检查
min与max是否用反。 - 重复图案在边缘有半颗点:UV 刚好为 1 的最外边界进入下一格,这是重复坐标的正常边界;有严格边缘要求时可缩小采样范围或裁掉最后一列。
练习
| 难度 | 题目 | 可见验收 |
|---|---|---|
| ⭐ | 中心正方形 | 居中的方形 |
| ⭐⭐ | 圆角矩形距离场 | 可调圆角 |
| ⭐⭐ | Min 组合十字 | 横竖矩形并集 |
| ⭐⭐ | 正弦波距离线 | 一条起伏波浪线 |
| ⭐⭐⭐ | Fract 重复 5×5 圆点 | 25 颗等大圆点 |
| ⭐⭐⭐ | 行列变化的圆点半径 | 圆点大小各异 |
| ⭐⭐⭐ | 三个半平面构成三角形 | 正确的三角形内外判断 |
| ⭐⭐⭐⭐ | 圆与三角形组合爱心 | 爱心轮廓 |
| ⭐⭐⭐⭐ | 指针切换八种形状 | 指针左右移动切换图案 |