前几天看到日志里面一个物体抖动的效果,问下了是通过手绘实现的。趁着五一劳动节有空,就劳动一下尝试使用代码去模拟这样一个效果,下面代码是用 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, sampler2Dtexture) {
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);
} elseif (pattern == 1) {
offset = floor(sqrt(pow(width + 0.5, 2) - x * x));
} elseif (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) {
returntrue;
}
}
}
returnfalse;
}
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 不太熟悉,暂时不知道怎么实现。最终的效果图在下面,代码放在 https://github.com/mnikn/Godot-Shader/blob/master/addons/shaders/SpriteShake.gdshader
暂无关于此日志的评论。