GLSL中的smoothstep函数详解

前言

在GLSL(OpenGL Shading Language)编程中,smoothstep函数是一个非常有用且常用的内置函数。它能够在两个值之间创建平滑的插值,广泛应用于边缘软化、渐变效果、动画过渡等场景。本文将深入介绍smoothstep函数的用法、数学原理和实际应用。

smoothstep函数基础

函数签名

1
2
3
4
5
6
7
8
9
10
// 基本形式
float smoothstep(float edge0, float edge1, float x)
vec2 smoothstep(vec2 edge0, vec2 edge1, vec2 x)
vec3 smoothstep(vec3 edge0, vec3 edge1, vec3 x)
vec4 smoothstep(vec4 edge0, vec4 edge1, vec4 x)

// 混合形式
vec2 smoothstep(float edge0, float edge1, vec2 x)
vec3 smoothstep(float edge0, float edge1, vec3 x)
vec4 smoothstep(float edge0, float edge1, vec4 x)

参数说明

  • edge0: 下边界值,当x <= edge0时返回0.0
  • edge1: 上边界值,当x >= edge1时返回1.0
  • x: 输入值
  • 返回值: 在[0.0, 1.0]范围内的平滑插值结果

数学原理

smoothstep函数实现的是一个S形的平滑插值曲线,其数学公式为:

1
2
3
4
5
6
7
// smoothstep的等价实现
float mySmooth(float edge0, float edge1, float x) {
// 将x规范化到[0,1]范围
float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
// Hermite插值: 3t² - 2t³
return t * t * (3.0 - 2.0 * t);
}

函数特性

  1. 平滑性: 在边界处一阶导数为0,确保平滑过渡
  2. 单调性: 在[edge0, edge1]区间内严格递增
  3. 边界行为: 在边界外返回0或1,无超调

与其他插值函数对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 线性插值 - 存在尖锐边界
float linearStep(float edge0, float edge1, float x) {
return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
}

// smoothstep - S形平滑曲线
float smoothStep(float edge0, float edge1, float x) {
float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
return t * t * (3.0 - 2.0 * t);
}

// smootherstep - 更平滑的曲线
float smootherStep(float edge0, float edge1, float x) {
float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
}

基本用法示例

1. 简单的边缘软化

1
2
3
4
5
6
7
8
9
10
11
12
13
// Fragment Shader - 软化圆形边缘
void main() {
vec2 center = vec2(0.5, 0.5);
float dist = distance(gl_FragCoord.xy / resolution.xy, center);

// 硬边缘
float hardCircle = step(0.3, dist);

// 软边缘
float softCircle = smoothstep(0.25, 0.35, dist);

gl_FragColor = vec4(vec3(1.0 - softCircle), 1.0);
}

2. 渐变效果

1
2
3
4
5
6
7
8
9
10
11
12
// 水平渐变
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;

// 线性渐变
float linear = uv.x;

// 平滑渐变
float smooth = smoothstep(0.0, 1.0, uv.x);

gl_FragColor = vec4(vec3(smooth), 1.0);
}

实际应用场景

1. 雾效实现

1
2
3
4
5
6
7
8
9
10
11
12
13
uniform float fogNear;
uniform float fogFar;
uniform vec3 fogColor;

void main() {
float depth = gl_FragCoord.z / gl_FragCoord.w;

// 使用smoothstep创建平滑的雾效
float fogFactor = smoothstep(fogNear, fogFar, depth);

vec3 finalColor = mix(objectColor, fogColor, fogFactor);
gl_FragColor = vec4(finalColor, 1.0);
}

2. 材质混合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform float mixFactor;

void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;

vec4 color1 = texture2D(texture1, uv);
vec4 color2 = texture2D(texture2, uv);

// 平滑的材质过渡
float factor = smoothstep(0.3, 0.7, mixFactor);
vec4 finalColor = mix(color1, color2, factor);

gl_FragColor = finalColor;
}

3. 动画过渡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
uniform float time;

void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;

// 创建脉动效果
float pulse = sin(time * 2.0) * 0.5 + 0.5;
float radius = smoothstep(0.0, 1.0, pulse) * 0.5;

float dist = distance(uv, vec2(0.5));
float circle = 1.0 - smoothstep(radius - 0.05, radius + 0.05, dist);

gl_FragColor = vec4(vec3(circle), 1.0);
}

4. 边缘检测和轮廓

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
uniform sampler2D inputTexture;
uniform vec2 resolution;

void main() {
vec2 uv = gl_FragCoord.xy / resolution;

// 采样周围像素
float tl = texture2D(inputTexture, uv + vec2(-1.0, -1.0) / resolution).r;
float tr = texture2D(inputTexture, uv + vec2(1.0, -1.0) / resolution).r;
float bl = texture2D(inputTexture, uv + vec2(-1.0, 1.0) / resolution).r;
float br = texture2D(inputTexture, uv + vec2(1.0, 1.0) / resolution).r;

// 计算梯度
float gradient = abs(tl - br) + abs(tr - bl);

// 使用smoothstep创建清晰的边缘
float edge = smoothstep(0.1, 0.3, gradient);

gl_FragColor = vec4(vec3(edge), 1.0);
}

高级技巧

1. 多重smoothstep组合

1
2
3
4
5
6
7
8
// 创建多段渐变
float multiStep(float x) {
float step1 = smoothstep(0.0, 0.3, x);
float step2 = smoothstep(0.3, 0.7, x);
float step3 = smoothstep(0.7, 1.0, x);

return step1 * 0.3 + step2 * 0.4 + step3 * 0.3;
}

2. 自定义缓动函数

1
2
3
4
5
6
7
8
9
// 缓入缓出
float easeInOut(float t) {
return smoothstep(0.0, 1.0, smoothstep(0.0, 1.0, t));
}

// 弹性效果
float elastic(float t) {
return smoothstep(0.0, 1.0, t) * (1.0 + sin(t * 3.14159 * 4.0) * 0.1);
}

3. 向量化操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 对RGB通道分别应用smoothstep
vec3 colorTransition(vec3 color, float factor) {
return smoothstep(vec3(0.2), vec3(0.8), vec3(factor));
}

// 创建彩虹效果
vec3 rainbow(float t) {
vec3 a = vec3(0.5, 0.5, 0.5);
vec3 b = vec3(0.5, 0.5, 0.5);
vec3 c = vec3(1.0, 1.0, 1.0);
vec3 d = vec3(0.0, 0.33, 0.67);

return a + b * cos(6.28318 * (c * t + d));
}

性能考虑

1. 内置函数优势

1
2
3
4
5
6
// 推荐:使用内置smoothstep
float result = smoothstep(0.0, 1.0, x);

// 不推荐:手动实现(除非需要自定义行为)
float t = clamp((x - 0.0) / (1.0 - 0.0), 0.0, 1.0);
float result = t * t * (3.0 - 2.0 * t);

2. 预计算优化

1
2
3
4
5
6
7
8
// 对于常量参数,可以预计算
uniform float precomputedRange; // = 1.0 / (edge1 - edge0)
uniform float precomputedOffset; // = -edge0 / (edge1 - edge0)

float optimizedSmoothstep(float x) {
float t = clamp(x * precomputedRange + precomputedOffset, 0.0, 1.0);
return t * t * (3.0 - 2.0 * t);
}

调试技巧

1. 可视化smoothstep曲线

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;

// 绘制函数曲线
float x = uv.x;
float y = smoothstep(0.2, 0.8, x);

// 显示曲线
float line = abs(uv.y - y) < 0.01 ? 1.0 : 0.0;

gl_FragColor = vec4(vec3(line), 1.0);
}

2. 参数调试

1
2
3
4
5
6
7
8
uniform float debugEdge0;
uniform float debugEdge1;
uniform float debugInput;

void main() {
float result = smoothstep(debugEdge0, debugEdge1, debugInput);
gl_FragColor = vec4(vec3(result), 1.0);
}

常见问题和解决方案

1. 边界值相等

1
2
3
4
5
// 错误:edge0 == edge1会导致除零
float bad = smoothstep(0.5, 0.5, x);

// 解决:确保边界值不同
float safe = smoothstep(0.5, 0.5 + 0.001, x);

2. 参数顺序

1
2
3
// 注意:edge0应该小于edge1
float correct = smoothstep(0.2, 0.8, x); // 正确
float incorrect = smoothstep(0.8, 0.2, x); // 结果会反转

总结

smoothstep函数是GLSL中一个强大而优雅的工具,它提供了:

  1. 数学上的优美性: S形曲线确保平滑过渡
  2. 实用性: 广泛适用于各种图形效果
  3. 性能: 硬件优化的内置实现
  4. 灵活性: 支持标量和向量操作

掌握smoothstep函数的使用技巧,能够显著提升shader编程的效果和效率。无论是创建艺术效果还是解决实际的渲染问题,smoothstep都是不可或缺的工具。

参考资源