Shader从入门到放弃(二) —— 常见GLSL内置函数

本文正在参加「金石计划 . 瓜分6万现金大奖」

前言

接上文,在Shader从入门到放弃 —— Shader编程简介及坐标系绘制 - 掘金 (juejin.cn)文章中,我们简单的介绍了什么是shader编程,并且利用shadertoy来进行shader编程。在上文中,我们完成了坐标系的绘制工作,主要用到了这几点:

  1. uv坐标的矫正
  2. 如何绘制网格(对uv坐标乘上一个数并且取小数部分作为新的uv坐标)

今天,我们将要进行进一步的学习,我们要学习一些GLSL中常用的内置函数,为我们后面的学习打下坚实的基础。

常用的内置函数

接上文中的代码,我们将绘制网格的函数抽离出来单独的作为一个函数:

vec3 grid(vec2 uv) {
    vec3 color = vec3(1.0);

    vec2 cell = 1.0 - 2.0 * abs(fract(uv) - 0.5);
    // color = vec3(cell, 0.0);
    if(abs(uv.x) <= 2. * fwidth(uv.x)) {
        color = vec3(0.0, 1.0, 0.0);
    } else if(abs(uv.y) <= 2. * fwidth(uv.y)) {
        color = vec3(1.0, 0.0, 0.0);
    } else if(abs(cell.x) <= 2. * fwidth(uv.x) || abs(cell.y) < 2. * fwidth(uv.y)) {
        color = vec3(0.6);
    }
    return color;
}

复制代码

在Shader编程中有一条黄金准则:

能不用if则不用if. 具体是为什么,可以参考这篇文章:GPU 是如何工作的? - 掘金 (juejin.cn)

所以,我们现在要做的就是消除掉上述函数中的 if

现在我们开始介绍第一个内置函数 : step(edge, x)

step

step generates a step function by comparing x to edge. For element i of the return value, 0.0 is returned if x[i] < edge[i], and 1.0 is returned otherwise.

它的意思就是:如果x 小于某个值,则返回 0,如果大于等于这个值,则返回1。

所以上面的if语句我们可以写成:

// if(abs(uv.x) <= 2. * fwidth(uv.x)) {
//     color = vec3(0.0, 1.0, 0.0);
// }
// ===>

color = step(abs(uv.x), 2.0 * fwidth(uv.x)) * vec3(0.0, 1.0, 0.0);
复制代码

shadertoy.png

但是这样的话,好像我们之前画的坐标系的格子都看不见了。所以我们要想办法将他们融合起来。所以,现在我们介绍第二个函数:mix

mix

它的函数签名如下:

genType mix(genType x, genType y, genType a);

mix performs a linear interpolation between x and y using a to weight between them. The return value is computed as follows: x⋅(1−a)+y⋅a.

该函数的实际上执行的就是一个简单的 线性差值。 简单的说就是:当a = 1时,返回的值为y, 但a = 0时,返回x的值,如果返回的值为0.5,则返回 0.5x + 0.5y的值,同理,如果a = 0.2, 返回 0.8 * a + 0.2 * y

我们可以利用这个函数来做颜色叠加,颜色叠加的公式可以写为:

C o l o r = S r c . c o l o r ( 1 D s t . a ) + D s t . c o l o r D s t . a Color = Src.color * (1 - Dst.a) + Dst.color * Dst.a

这刚好符合上面的 mix 函数的定义。所以可以写出下面的式子:

color = mix(color, vec3(0.0, 1.0, 0.0), step(abs(uv.x), 2.0 * fwidth(uv.x)));
复制代码

对于其他的坐标我们都可以同理写出:


    color = mix(color, vec3(0.6), step(abs(_uv.x), 2.0 * fwidth(uv.x)));
    color = mix(color, vec3(0.6), step(abs(_uv.y), 2.0 * fwidth(uv.x)));
    color = mix(color, vec3(0.0, 1.0, 0.0), step(abs(uv.x), 2.0 * fwidth(uv.x)));
    color = mix(color, vec3(1.0, 0.0, 0.0), step(abs(uv.y), 2.0 * fwidth(uv.y)));
复制代码

OK,现在我们就已经完成了if 分支的替换工作。但是使用 step函数它有一个小小的缺陷,我们可以通过一张图看出来。

shadertoy.png

如上图所示,我们可以看出,当我们的坐标系发生了旋转的情况下,在线条的边缘会产生很多的锯齿。这是因为step 函数它不是返回0就是返回1,它发生了突变,没有一个平滑过渡的区间。

image.png

所以,现在我们介绍第三个函数:smoothstep

smoothstep

genType smoothstep(genType edge0, genType edge1, genType x)

smoothstep performs smooth Hermite interpolation between 0 and 1 when edge0 < x < edge1. This is useful in cases where a threshold function with a smooth transition is desired. smoothstep is equivalent to:

    genType t;  /* Or genDType t; */
    t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
    return t * t * (3.0 - 2.0 * t);
复制代码

smoothstep returns 0.0 if x ≤ edge0 and 1.0 if x ≥ edge1.

其函数图像如下:

image.png

我们可以直观的看出:当x < 0时,返回0,当 x > 1时,返回1,如果 x 处于0~1之间时,则有一个类似于线性插值的过渡,但是请注意,这里并不是线性插值,因为在接近两端的值的时候,该函数有一个平滑的过渡。所以我们可以通过这一点来对 step 函数进行替换以解决锯齿的问题。

我们可以写出下面的代码:

color = mix(color, vec3(0.6), smoothstep(3.0 * fwidth(uv.x), 1.0 * fwidth(uv.x), abs(cell.x)));

color = mix(color, vec3(0.6), smoothstep(3.0 * fwidth(uv.x), 1.0 * fwidth(uv.x), abs(cell.y)));
复制代码

结果如下:

shadertoy.png

我们可以发现在格子的一侧的锯齿情况有明显的改善,但是另一侧还是锯齿比较严重。这是因为我们格子的坐标是 0~1之间,但是我们是对 abs(x) 进行差值,abs(x)函数小于 3.0 * fwidth(uv.x) 的值始终都处于格子的一侧,所以我们需要对uv坐标进行一点修改。

修改如下:

    vec2 cell = 1.0 - 2.0 * abs(fract(uv) - 0.5);
    color = mix(color, vec3(0.6), smoothstep(3.0 * fwidth(uv.x), 1.0 * fwidth(uv.x), abs(cell.x)));
    color = mix(color, vec3(0.6), smoothstep(3.0 * fwidth(uv.x), 1.0 * fwidth(uv.x), abs(cell.y)));
复制代码

结果如下:

shadertoy.png

通过对比红色和绿色XY轴与灰色的格子线,我们可以清晰的看出格子上的锯齿有了明显的改善,这就是smoothstep的功劳!那么类似的,我们可以通过此方法来优化我们的XY轴。

最终优化效果如下:

shadertoy.png

总结

今天我们通过解决解决一系列的问题学习了以下三个内置函数:

  1. 为了解决掉if分支,我们使用了step 函数
  2. 为了解决颜色融合的问题,我们使用了 mix 函数,
  3. 为了解决线条锯齿的问题,我们使用了smoothstep 函数。

希望读者后续多多使用这几个内置函数,达到熟练的程度。我们下篇文章再见!

猜你喜欢

转载自juejin.im/post/7166939417599279112