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 的高级版。它在 edge0 和 edge1 之间生成一个平滑的 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. 绘制圆形
利用 distance 和 step。
// 距离中心的距离
float d = distance(vUv, vec2(0.5));
// 如果距离小于 0.3,step 返回 0(也就是黑色),反之白色。
// 我们想要圆内是白色,所以取反:
float circle = 1.0 - step(0.3, d);
2. 制作条纹
利用 sin 或 fract。
// 使用 sin 制作模糊条纹
float stripes = sin(vUv.x * 50.0);
// 使用 fract 制作锐利条纹
float sharpStripes = step(0.5, fract(vUv.x * 10.0));
📝 编程作业
难度:⭐⭐ 中等 | 预计时间:60 分钟
任务要求
综合运用 GLSL 内置函数,创建一个"函数展示画廊",展示各种函数的视觉效果。
具体要求
必须使用以下函数至少各一次:
mix()- 创建颜色渐变,可以配合 uvstep()- 创建硬边缘smoothstep()- 创建平滑过渡的边缘sin()/cos()- 创建波浪效果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(...)))
参考资源
- 完整作业清单:编程作业清单.md