05 smoothstep 与平滑边缘
学完本章能做什么
你将能解释 smoothstep 的三个参数,控制边缘位置和过渡宽度,并用多个平滑遮罩做出柔和条带与矩形。
先看现象
打开示例,默认画面上半部分使用 step,下半部分使用 smoothstep。拖动“羽化半宽”:上半部分的分界仍突然改变;下半部分出现更宽或更窄的颜色过渡。切换到“柔和竖条”或“柔和矩形”,可以看到两侧或四侧的边缘共同作用。
1. 三个参数分别做什么
float t = smoothstep( edge0, edge1, x );
在 edge0 < edge1 时,可以这样读:
x 所处范围 |
输出 |
|---|---|
x <= edge0 |
0.0 |
edge0 < x < edge1 |
从 0 平滑变化到 1 |
x >= edge1 |
1.0 |
与 step 只有一个跳变位置不同,smoothstep 有一个过渡区间。示例使用:
float soft = smoothstep(
uCenter - uFeather,
uCenter + uFeather,
vUv.x
);
过渡区间中心是 uCenter,总宽度是 2.0 * uFeather。例如中心为 0.5、半宽为 0.1,颜色从 x = 0.4 开始变化,到 x = 0.6 达到另一端。若只是把中心从 0.5 改成 0.7,边缘会移动而宽度不变。
不要倒置两个 edge。 GLSL 对 edge0 >= edge1 的结果没有定义。想让颜色从 1 降到 0,应写 1.0 - smoothstep(edge0, edge1, x),仍保持 edge0 < edge1。
2. 为什么看起来比线性过渡柔和
可以把 smoothstep 想成先把输入归一化到 0.0~1.0,再改变它的变化速度:
float q = clamp( ( x - edge0 ) / ( edge1 - edge0 ), 0.0, 1.0 );
float t = q * q * ( 3.0 - 2.0 * q );
中间值仍为 0.5,但在区间起点和终点附近,变化会放慢,接到两侧常量区域时更自然。这里的表达式用于理解原理,实际代码直接调用 smoothstep 即可。
smoothstep 只根据当前片元的一个输入值计算输出;它不会读取周围像素,也不是图像模糊滤镜。过渡宽度若比屏幕上的一个像素还窄,仍可能出现锯齿或闪烁。第 21 章讲 SDF 边缘时会结合屏幕导数处理这一问题。
3. 同一边界的硬、软比较
本章示例在上半区域选硬边,下半区域选软边:
float hard = step( uCenter, vUv.x );
float soft = smoothstep( uCenter - uFeather, uCenter + uFeather, vUv.x );
float mask = vUv.y >= 0.5 ? hard : soft;
最后仍用 mix(darkColor, lightColor, mask) 输出。两部分的中心一样,因此唯一改变的是边界附近如何过渡。
4. 把两个软边夹成一条带
一条从左进入、从右离开的软带可以写成:
float left = smoothstep( leftEdge - feather, leftEdge + feather, vUv.x );
float right = 1.0 - smoothstep( rightEdge - feather, rightEdge + feather, vUv.x );
float verticalBand = left * right;
left 从 0 升到 1;right 从 1 降到 0;相乘后只有两者都接近 1 的中间区域是亮的。与第 4 章的 step 差值带不同,这两个边缘拥有连续过渡。示例以 uCenter ± 0.18 确定两条边界。
要让中间保持明显的平坦区域,左右边缘应留出足够距离。如果 feather 过大,两个过渡区间互相覆盖,中间也可能达不到 1.0。这并非编译错误,而是两个软遮罩相乘的结果。
5. 从软带到软矩形
横向再做一次“进入与离开”的遮罩,然后与竖带相乘:
float bottom = smoothstep( 0.32 - feather, 0.32 + feather, vUv.y );
float top = 1.0 - smoothstep( 0.68 - feather, 0.68 + feather, vUv.y );
float rectangle = verticalBand * bottom * top;
乘法在四角会把两个较小的软值再次相乘,因此角落通常比单侧边缘更暗。若改用 min(verticalBand, bottom * top),角落的过渡外观会不同。后续 SDF 章节会给出按距离控制整个轮廓边缘的方法。
常见问题
- **看不到柔化:**增大羽化半宽,检查过渡区间是否真的覆盖可见 UV;在高分辨率屏幕上,极窄的过渡可能不足一个像素。
- **颜色方向反了:**使用
1.0 - smoothstep(edge0, edge1, x),不要交换两个 edge。 - **中心亮度偏低:**左右过渡区间可能重叠;缩小
feather或加宽条带。 - **矩形角落颜色不同:**多个软遮罩相乘会改变角落值,这是组合方式造成的。
- **误以为网格分段太少:**本章的过渡在片元着色器中计算;先检查 UV 与羽化宽度。
本章小结
smoothstep 把阈值扩展成一个可控的平滑区间。两个相反方向的软边构成条带,横竖条带再组合成矩形。下一章会利用 floor 与 fract 把同一段局部图案复制到许多 UV 单元中。