Smoothstep in Shader

445 阅读3分钟

smoothstep函数是GLSL中最重要的函数之一。它允许我们创建不同的形状,应用过渡效果,模糊图像等。然而,在大多数教科书中,并没有充分展示它的威力。本文旨在介绍其功能。

smoothstep 函数是 GLSL 中的内置函数,用于在两个值之间创建平滑的过渡。 它有三个参数: edge0edge1xedge0edge1 设置过渡发生的范围, x 是要平滑的值。

smoothstep 函数对于小于 edge0x 值返回 0.0,对于大于 edge1x 值返回 1.0。对于 edge0edge1 之间的 x 值, smoothstep 返回在 0.0 和 1.0 之间的平滑过渡。

推導

现在我们知道这个函数的效果,但它的公式是什么?我在Desmos中展示了结果,您可以尝试调整函数。

smoothstep

首先,我们简单创建两个函数:

a = x 2a\ =\ x\ ^{2}
b = 1  (x)2b\ =\ 1\ -\ \left(x\right)^{2}

图形中的函数如下所示:

www.desmos.com/calculator/…

控制x将它们组合在一起:

a(1  x) + bxa\left(1\ -\ x\right)\ +\ bx

在 x 的合并之后:

 k2(3  2k)\ k^{2}\cdot\left(3\ -\ 2k\right)

www.desmos.com/calculator/…

为了清晰,我们用 k 替换 x。

我们希望 y 值在 0 和 1 之间受限。使用最大值和最小值来创建夹紧函数以帮助我们。

k =max(0, min(1,  (x  t1) ))k\ =\max\left(0,\ \min\left(1,\ \ \left(x\ -\ t_{1}\right)\ \right)\right)

www.desmos.com/calculator/…

t1是斜率的偏移量变量。您可以控制它来查看变化。但是倾斜度是恒定的,因此我们需要另一个变量来控制它。

k =max(0, min(1,  (x  t1)  1t2 ))k\ =\max\left(0,\ \min\left(1,\ \ \left(x\ -\ t_{1}\right)\ \frac{\ 1}{t_{2}}\ \right)\right)

随着t2的增加,倾斜角度变平。两个变量是相互独立的,我们希望t1表示斜坡的起点,t2表示终点。

k =max(0, min(1,  (x  t1)  1(t2  t1)))k\ =\max\left(0,\ \min\left(1,\ \ \left(x\ -\ t_{1}\right)\ \ \frac{1}{\left(t_{2}\ -\ t_{1}\right)}\right)\right)

www.desmos.com/calculator/…

设置t1为0.5,t2为2。上面的图表显示当x为0.5时,k为0。当x为2时,y为1。为了使其平滑移动,将k与先前的函数组合:

y = k2(3  2k)y\ =\ k^{2}\cdot\left(3\ -\ 2k\right)

漸变

让我们在ShaderToy中玩shader。 smoothstep的最简单示例是渐变背景:

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;

    vec3 col = vec3(.0);
    float i = smoothstep(.5, .56, uv.x);
    col = vec3(i);
    
    // Output to screen
    fragColor = vec4(col,1.0);
}

bg.png

使用绝对值函数,我们可以制作一些形状:

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = fragCoord/iResolution.xy;
    // make coordinate to be center
    uv -= .5;

    vec3 col = vec3(.0);
    float i = 1. - smoothstep(.2, .21, abs(uv.x));
    col = vec3(i);
    
    // Output to screen
    fragColor = vec4(col,1.0);
}

bend.png

蛇叉

现在让我们制作一些复杂的东西。我们想要一个像这样的形状:

fork.png

首先,我们制作两条带子:

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = (fragCoord - .5 * iResolution.xy)/iResolution.y;
    vec3 col = vec3(0);
    float m = smoothstep(0.05, 0.0, abs(abs(uv.x) - .1));
    col += m;
    // Output to screen
    fragColor = vec4(col,1.0);
}

two_band.png

上面的代码中abs函数返回一个数的绝对值。在代码的上下文中,它被用来确定形状中心和每个像素之间的距离。通过取uv.x的绝对值并从0.1中减去它,我们可以创建以形状中心为中心的两个带。然后使用smoothstep函数在两个带之间创建平滑的过渡,从而得到一个弯曲的形状。我们想创建一个叉子,隨着uv.y 变化分离。为此,我们对偏移量进行了一些改变:

float m = smoothstep(0.05, 0.0, abs(abs(uv.x) - smoothstep(0., .5, uv.y) * .2));

fork2.png

0.2的乘法是为了减小smoothstep的值。

通过mix函数的帮助,我们可以给线条的大小加入一些渐变。

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from 0 to 1)
    vec2 uv = (fragCoord - .5 * iResolution.xy)/iResolution.y;
    vec3 col = vec3(0);
    float fork = smoothstep(0., .5, uv.y);
    float m = smoothstep(mix(.05, .03, fork), 0.0, abs(abs(uv.x) - smoothstep(0., .5, uv.y) * .1));
    col += m;
    // Output to screen
    fragColor = vec4(col,1.0);
}

finish.png

Shadertoy