ShaderJoy —— 伸缩、旋转的 “+” 效果【GLSL】

2,348 阅读3分钟

正文第一句加入“我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!

效果图

  1. 仅伸缩的 “+”

juejuejue.gif

  1. 加上旋转的 “+”

juejuejue.gif

算法思路

划分网格

首先,需要针对 uv 坐标划分网格

  • ouv 的可视化 image.png

对应代码如下

/// @note 划分格子,确保一个格子内是同一值
uv *= SIZE;
vec2 ouv = floor(uv) / SIZE;

接着,对每一小格的 uv 进行调整,使新的 uv 坐标的中心位于小格的 (0., 0.) 处

  • uv 的可视化 image.png

对应的代码如下

uv = fract(uv) - 0.5;

径向差分

划分好网格之后,下一步要实现的效果,需要一个 a 值,它从屏幕中心沿(圆的)半径向外的不同的格子所代表的值不同,但在同一半径的圆圈上,该值是相同的。

image.png

该值后续会搭配时间变化被进一步用作 “伸缩” 和 “旋转” 的计算。

对应的代码如下

float a = length(ouv * 5.0) - iTime * 2.5; ///< 随着时间外扩

绘制 “+”

“+” 实际是由一横一竖两条线段拼接而成的,此处分别使用了 uv.xuv.y 的绝对值

对应代码如下

float mask = smoothstep(s + sm, s, abs(uv.x)); ///< 绘制 "|"
mask += smoothstep(s + sm, s, abs(uv.y));      ///< 绘制 "——"
  • mask 可视化如下 image.png

值得注意的是, 各个 “+” 的中心位于各个小格 uv 的 (0., 0.) 点处

image.png

PS: smoothstep 是平滑阶梯函数,shader 里面使用十分频繁,网上资料也很多,我这里不做过多讲解,感兴趣的朋友可以去自行搜索

旋转的效果

由于旋转就是套用旋转矩阵的公式,这里就不再赘述,对应的代码如下所示

/// @note 旋转 uv
float ca = cos(a);
float sa = sin(a);
mat2 rot = mat2(ca, - sa, sa, ca);
uv *= rot;

为了看得更清楚,我将格子调整为 5*5 大小,(同时也再一次印证了 “+” 的中心位于各个小格 uv 的 (0., 0.) 点处)

juejuejue.gif

伸缩的效果

这里用所用的 ca,即我们前一个步骤所计算的 cos(a),通过它进一步构造了一个遮罩,来实现对 “+” 的伸缩效果。对应的代码如下

s = 0.25 + (ca * 0.5 + 0.5) * 0.2; ///< 取值范围在 [0.25, 0.45],用于控制 “+” 的缩放

mask *= smoothstep(s, s - sm, abs(uv.x));
mask *= smoothstep(s, s - sm, abs(uv.y));

juejuejue.gif

介绍完了基本的算法思路,接下来我们直接面对完整代码吧(不用害怕,代码不多,而且我详细注释了

代码与详细注释

// https://www.shadertoy.com/view/WdBSWd

#define SIZE 25.0
#define COL_BLACK vec3(23, 32, 38) / 255.0
#define COL_WHITE vec3(245, 248, 250) / 255.0

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    /// @note 等比例变换(抵消窗口的缩放对 uv 坐标的影响)
    /// 等价于 (fragCoord - iResolution.xy * 0.5) / iResolution.xy * vec2(iResolution.x/iResolution.y, 1.)
    ///  uv 坐标取值范围是 vec2([-.5, .5] * iResolution.x/iResolution.y, [-.5, .5])
    vec2 uv = (fragCoord - iResolution.xy * 0.5) / iResolution.y;

    /// @note 划分格子,确保一个格子内是同一值
    uv *= SIZE;
    vec2 ouv = floor(uv) / SIZE;
    // fragColor = vec4(ouv, 0., 1.);
    // return;
    /// @note 对每一小格的 uv 进行调整,使新的 uv 坐标的中心位于小格的 (0., 0.) 处
    uv = fract(uv) - 0.5;
    // fragColor = vec4(uv, 0., 1.);
    // return;

    /// @note 确保不同格子(从屏幕中心沿半径向外所呈现的)旋转角度不同
    /// 同一半径上,旋转角度相同
    float a = length(ouv * 5.0) - iTime * 2.5;  ///< 时间的变化使其不断外扩
    // fragColor = vec4(length(ouv * 5.0)); 
    // return;

    /// @note 旋转 uv
    float ca = cos(a);
    float sa = sin(a);
    mat2 rot = mat2(ca, - sa, sa, ca);
    // uv *= rot;

    float sm = 1.0 / (iResolution.x / SIZE) * 2.0;
    float s = 0.05;
    
    /// @note 绘制 “+” 
    float mask = smoothstep(s + sm, s, abs(uv.x));
    mask += smoothstep(s + sm, s, abs(uv.y));

    s = 0.25 + (ca * 0.5 + 0.5) * 0.2; ///< 取值范围在 [0.25, 0.45],用于控制 “+” 的缩放

    mask *= smoothstep(s, s - sm, abs(uv.x));
    mask *= smoothstep(s, s - sm, abs(uv.y));

    vec3 col = mix(COL_BLACK, COL_WHITE, mask);

    fragColor = vec4(col, 1.0);
}