Shader code
shader_type spatial;
render_mode unshaded;
uniform sampler2D noise_texture;
group_uniforms depth;
uniform int layers : hint_range(0, 64, 1) = 8;
uniform float depth_amount = 1.0;
uniform float fade_amount : hint_range(0.0, 16.0, 0.01) = 1.0;
uniform float flow_speed : hint_range(0.0, 2.0, 0.01) = 0.15; // скорость "затягивания"
uniform float warp_amount : hint_range(0.0, 1.0, 0.01) = 0.25; // сила деформации края шумом
vec2 to_polar(vec2 uv) {
uv = uv * 2.0 - 1.0;
float radius = length(uv);
float angle = atan(uv.x, uv.y);
return vec2(angle / PI, radius) / 2.0;
}
vec2 parallax(float depth, vec3 n, vec3 t, vec3 v) {
vec3 normal = normalize(n);
vec3 tangent = normalize(t);
vec3 bitangent = cross(normal, tangent);
vec3 view = normalize(v);
vec3 view_tangent = vec3(dot(view, tangent), dot(view, bitangent), dot(view, normal));
vec2 offset = (view_tangent.xy / max(view_tangent.z, 0.001)) * depth;
return vec2(-offset.x, offset.y);
}
void fragment() {
float value = 0.0;
for (int i = 0; i < layers; i++) {
// t теперь "бежит" по времени и заворачивается через fract —
// это и даёт бесконечное движение слоёв внутрь дырки
float t = fract(float(i) / float(layers) - TIME * flow_speed);
float depth = t * depth_amount;
vec2 offset = parallax(depth, NORMAL, TANGENT, VIEW);
vec2 circle_uv = UV + offset;
// шум для искажения формы
vec2 noise_uv = to_polar(circle_uv);
noise_uv.y += t * 0.5;
float noise = texture(noise_texture, noise_uv).r;
// деформируем сам круг шумом — край перестаёт быть ровной окружностью
vec2 warped = (circle_uv * 2.0 - 1.0);
warped += (noise - 0.5) * warp_amount;
float circle = length(warped);
circle = clamp((circle - 0.2) / 0.8, 0.0, 1.0);
// smoothstep вместо жёсткого step — мягкие полутона как на гифке
float layer_value = 1.0 - smoothstep(0.0, 0.15, circle);
layer_value *= pow(1.0 - t, fade_amount);
value = max(value, layer_value);
}
ALBEDO = vec3(value);
}
Live Preview