Skip to main content

06. 常用内置函数详解

GLSL 提供了极其丰富的内置数学函数。熟练使用它们是写出 Shader 的关键。不要自己造轮子!

1. 基础数学

  • sin(x), cos(x), tan(x): 三角函数。注意输入是弧度
  • pow(x, y): $x$ 的 $y$ 次方。
  • exp(x): $e^x$。
  • log(x): 自然对数。
  • sqrt(x): 开根号。
  • abs(x): 绝对值。
  • sign(x): 符号函数(正数返回 1.0,负数返回 -1.0,0 返回 0.0)。
  • floor(x): 向下取整。
  • ceil(x): 向上取整。
  • fract(x): 取小数部分。即 $x - floor(x)$。非常常用,用于制造重复纹理。
  • mod(x, y): 取模(余数)。

2. 限制与映射

  • min(a, b): 取较小值。
  • max(a, b): 取较大值。
  • clamp(x, minVal, maxVal): 将 $x$ 限制在 $[minVal, maxVal]$ 之间。
    • 如果 $x < minVal$ 返回 $minVal$。
    • 如果 $x > maxVal$ 返回 $maxVal$。

3. 插值三剑客 (必背)

这三个函数是 Shader 编程中最强大的工具。

A. mix(a, b, t) - 线性插值

公式:$a \cdot (1-t) + b \cdot t$

  • 当 $t=0$ 时,返回 $a$。
  • 当 $t=1$ 时,返回 $b$。
  • 当 $t=0.5$ 时,返回 $a$ 和 $b$ 的中点。

用途:颜色混合、位置过渡。

vec3 color = mix(red, blue, 0.5); // 紫色

B. step(edge, x) - 阶跃函数

公式:如果 $x < edge$ 返回 0.0,否则返回 1.0。

用途:硬边缘、开关、二值化。

// 如果 uv.x 小于 0.5 变黑,否则变白
float c = step(0.5, uv.x);

C. smoothstep(edge0, edge1, x) - 平滑阶跃

这是 step 的高级版。它在 edge0edge1 之间生成一个平滑的 S 形曲线过渡(Hermite 插值),而不是突变。

  • $x < edge0$: 返回 0.0
  • $x > edge1$: 返回 1.0
  • 中间: 返回 0.0 到 1.0 之间的平滑值

用途:抗锯齿边缘、平滑过渡、雾效。

// 在 0.4 到 0.6 之间产生平滑过渡
float c = smoothstep(0.4, 0.6, uv.x);

4. 几何函数

  • length(v): 向量长度。
  • distance(a, b): 两点距离。
  • dot(a, b): 点积。
  • cross(a, b): 叉积。
  • normalize(v): 归一化。
  • reflect(I, N): 计算反射向量。$I$ 是入射向量,$N$ 是法线。常用于环境贴图反射。

💡 常用模式示例

1. 绘制圆形 利用 distancestep

// 距离中心的距离
float d = distance(vUv, vec2(0.5));
// 如果距离小于 0.3,step 返回 0(也就是黑色),反之白色。
// 我们想要圆内是白色,所以取反:
float circle = 1.0 - step(0.3, d);

2. 制作条纹 利用 sinfract

// 使用 sin 制作模糊条纹
float stripes = sin(vUv.x * 50.0);

// 使用 fract 制作锐利条纹
float sharpStripes = step(0.5, fract(vUv.x * 10.0));

📝 编程作业

难度:⭐⭐ 中等 | 预计时间:60 分钟

任务要求

综合运用 GLSL 内置函数,创建一个"函数展示画廊",展示各种函数的视觉效果。

具体要求

必须使用以下函数至少各一次:

  1. mix() - 创建颜色渐变,可以配合 uv
  2. step() - 创建硬边缘
  3. smoothstep() - 创建平滑过渡的边缘
  4. sin() / cos() - 创建波浪效果
  5. fract() - 创建重复图案

💡 为什么能实现重复图案?

fract() 会把任何数字映射到 [0, 1) 范围内:

fract(0.3) = 0.3
fract(1.3) = 0.3 ← 重复了!
fract(2.3) = 0.3 ← 又重复了!
fract(3.3) = 0.3 ← 继续重复!

所以如果你把 UV 坐标放大(比如乘以 5),然后取 fract()

// UV 范围是 0.0 到 1.0
// 乘以 5 后:0.0, 0.2, 0.4, ..., 4.8, 5.0
// fract 后:0.0, 0.2, 0.4, ..., 0.8, 0.0 ← 又从头开始了!
vec2 repeatedUv = fract(vUv * 5.0);

这样就能在 0-1 的范围内重复 5 次相同的图案!

代码框架

// 片段着色器
precision mediump float;

varying vec2 vUv;
uniform float uTime;

void main() {
vec3 color = vec3(0.0);

// 将屏幕分成多个区域,每个区域展示不同的函数效果
float region = floor(vUv.x * 5.0); // 分成 5 个区域
vec2 localUv = fract(vUv * 5.0); // 每个区域的局部 UV

// 💡 为什么需要局部 UV?
//
// 原始 vUv 范围是 0.0 到 1.0(覆盖整个屏幕)
// 如果直接在每个区域使用 vUv,图案会被拉伸到整个屏幕,而不是在每个区域内重复
//
// 例子:
// - 区域 0:vUv.x 范围是 0.0 到 0.2(只占屏幕的 1/5)
// - 区域 1:vUv.x 范围是 0.2 到 0.4
// - ...
//
// 使用 localUv = fract(vUv * 5.0):
// - 区域 0:localUv.x 范围是 0.0 到 1.0(完整的 0-1 范围!)
// - 区域 1:localUv.x 范围也是 0.0 到 1.0(完整的 0-1 范围!)
// - 每个区域都能显示完整的图案,而不是被压缩

// 区域 0:mix() 颜色渐变
if (region < 1.0) {
vec3 color1 = vec3(1.0, 0.0, 0.0);
vec3 color2 = vec3(0.0, 0.0, 1.0);
color = mix(color1, color2, localUv.x);
}
// 区域 1:step() 硬边缘
else if (region < 2.0) {
float edge = step(0.5, localUv.x);
color = vec3(edge);
}
// 区域 2:smoothstep() 平滑过渡
else if (region < 3.0) {
float smooth = smoothstep(0.3, 0.7, localUv.x);
color = vec3(smooth);
}
// 区域 3:fract() 重复图案
else if (region < 4.0) {
float pattern = fract(localUv.x * 5.0);
color = vec3(pattern);
}
// 区域 4:sin() 波浪
else {
float wave = sin(localUv.x * 10.0 + uTime) * 0.5 + 0.5;
color = vec3(wave);
}

gl_FragColor = vec4(color, 1.0);
}

检查清单

  • 使用了所有要求的函数
  • 每个效果都清晰可见
  • 代码结构清晰,易于理解
  • 可以正常运行,没有错误

扩展挑战

  • 🌟 创建一个随时间变化的动画,展示不同函数的组合效果
  • 🌟 添加交互:鼠标位置影响某些参数
  • 🌟 尝试组合多个函数创建复杂效果(如 smoothstep(fract(...))

参考资源