Godot Shader 实现抖动效果

635 阅读2分钟

前几天看到日志里面一个物体抖动的效果,问下了是通过手绘实现的。趁着五一劳动节有空,就劳动一下尝试使用代码去模拟这样一个效果,下面代码是用 Godot 3.4 实现的。

抖动一般分为两大部分,物体做位置上的轻微移动,和物体的轮廓要轻微发生变化。位置的移动还好说,轮廓上的变化就需要用 shader 去模拟这样一个效果了。

首先从简单的部分开始,位置上的轻微移动,这个只需要使用 vertex shader 就好了,根据时间来做位置的移动,代码如下:


uniform float shake_move_speed: hint_range(1.0, 100.0) = 15.0;
uniform vec2 shake_move_direction = vec2(0.8, 0.9);
uniform vec2 shake_move_size = vec2(0.2, 0.2);
​
void vertex() {
    float time = TIME * shake_move_speed;
    VERTEX.x += cos(time * shake_move_direction.x + UV.x) * shake_move_size.x;
    VERTEX.y += sin(time * shake_move_direction.y + UV.y) * shake_move_size.y;
}

接下来就是比较麻烦的轮廓部分了,大体想法是通过动态添加外轮廓来模拟轮廓的变化。我去找了份画轮廓的 shader 代码,然后稍微改造了一下,只需要用到 fragment shader 就能实现,代码:


uniform int pattern : hint_range(0, 2) = 0; // diamond, circle, square
uniform float shake_speed: hint_range(1.0, 100.0) = 10.0;
uniform float shake_size: hint_range(0.0, 5.0) = 0.2;
​
bool hasContraryNeighbour(float width, vec2 uv, vec2 texture_pixel_size, sampler2D texture) {
    for (float i = -ceil(width); i <= ceil(width); i++) {
        float x = abs(i) > width ? width * sign(i) : i;
        float offset;
        
        if (pattern == 0) {
            offset = width - abs(x);
        } else if (pattern == 1) {
            offset = floor(sqrt(pow(width + 0.5, 2) - x * x));
        } else if (pattern == 2) {
            offset = width;
        }
        
        for (float j = -ceil(offset); j <= ceil(offset); j++) {
            float y = abs(j) > offset ? offset * sign(j) : j;
            vec2 xy = uv + texture_pixel_size * vec2(x, y);
            
            if ((xy != clamp(xy, vec2(0.0), vec2(1.0)) || texture(texture, xy).a == 0.0) == false) {
                return true;
            }
        }
    }
    
    return false;
}
​
void fragment() {
    vec2 uv = UV;
    float width = 0.0;
    float time = TIME * shake_speed;
    
    if (step(abs(cos(time + UV.y + UV.x)), 0.5) == 1.0) {
        width += shake_size;
    }
    
    COLOR = texture(TEXTURE, uv);
    
    if (hasContraryNeighbour(width, uv, TEXTURE_PIXEL_SIZE, TEXTURE) ) {
        COLOR.rgb = COLOR.rgb;
        COLOR.a += (1.0 - COLOR.a);
    }
}

组合起来一个简易的抖动效果就实现好了,效果基本上能够勉强糊弄人,不过比手绘还是差了不少,如果同时加上内轮廓的抖动效果应该会更好,但我对 shader 不太熟悉,暂时不知道怎么实现。最终的效果图在下面,代码放在 github.com/mnikn/Godot…

godot_shader_shake.gif