This is what I have been able to come up with:
shader_type canvas_item;
uniform float progress : hint_range(0.0, 1.0) = 0.0;
uniform float sprite_scale_begin : hint_range(0.0, 5.0) = 1.0;
uniform float sprite_scale_end : hint_range(0.0, 5.0) = 1.0;
uniform float particle_scale_begin : hint_range(0.0, 5.0) = 1.0;
uniform float particle_scale_end : hint_range(0.0, 5.0) = 1.0;
uniform sampler2D dissolve_texture;
void vertex() {
// GROW
VERTEX *= mix(sprite_scale_begin, sprite_scale_end, progress);
}
void fragment() {
// MOSAIC EFFECT
const float pixel_step = 4.0;
vec2 texure_size = 1.0 / TEXTURE_PIXEL_SIZE;
vec2 steps = texure_size/pixel_step;
vec2 uv = floor(UV * steps) / (steps - 1.0);
COLOR = texture(TEXTURE, uv);
// PARTICLE SIZE
vec2 fract_uv = fract(UV * steps);
vec2 direction = UV - 0.5;
vec2 quantized_direction = sign(direction);
float angle = PI * 1.25 - atan(quantized_direction.y, quantized_direction.x);
float angle_sin = sin(angle);
float angle_cos = cos(angle);
mat2 rotation_matrix = mat2(vec2(angle_cos, angle_sin), vec2(-angle_sin, angle_cos));
vec2 rotated_fract_uv = (rotation_matrix * (fract_uv - 0.5)) + 0.5;
float inverse_particle_scale = 1.0 / mix(particle_scale_begin, particle_scale_end, progress);
if (isinf(inverse_particle_scale)){
COLOR.a = 0.0;
}
else
{
float step_position = max(rotated_fract_uv.x, rotated_fract_uv.y) * inverse_particle_scale;
float inverse_sprite_scale = 1.0 / mix(sprite_scale_begin, sprite_scale_end, progress);
COLOR.a *= step(step_position, inverse_sprite_scale);
}
// DISSOLVE EFFECT
float dissolve = texture(dissolve_texture, uv).x;
dissolve = step(progress, dissolve);
COLOR.a *= dissolve;
}
Let us go part by part...
First of all, just as in your code progress
controls the animation:
uniform float progress : hint_range(0.0, 1.0) = 0.0;
Now, to make the sprite grow, I have defined these uniform
s:
uniform float sprite_scale_begin : hint_range(0.0, 5.0) = 1.0;
uniform float sprite_scale_end : hint_range(0.0, 5.0) = 1.0;
So I can compute what should be the scale of the sprite using progress
, and the code to actually scale it is in vertex
:
void vertex() {
// GROW
VERTEX *= mix(sprite_scale_begin, sprite_scale_end, progress);
}
The mosaic effect in fragment
begins with the pixel_step
constant, which I promoted from literals in your code:
// MOSAIC EFFECT
const float pixel_step = 4.0;
We continue with the texture_size
as you do:
// MOSAIC EFFECT
const float pixel_step = 4.0;
vec2 texure_size = 1.0 / TEXTURE_PIXEL_SIZE;
And how many steps does the texture has:
// MOSAIC EFFECT
const float pixel_step = 4.0;
vec2 texure_size = 1.0 / TEXTURE_PIXEL_SIZE;
vec2 steps = texure_size/pixel_step;
So we can get the uv
(which is at integers steps):
// MOSAIC EFFECT
const float pixel_step = 4.0;
vec2 texure_size = 1.0 / TEXTURE_PIXEL_SIZE;
vec2 steps = texure_size/pixel_step;
vec2 uv = floor(UV * steps) / (steps - 1.0);
I was tempted to extract UV * steps
as a variable, as we will use it later, but I couldn't figure out a good name for it.
And we can set the output COLOR
:
// MOSAIC EFFECT
const float pixel_step = 4.0;
vec2 texure_size = 1.0 / TEXTURE_PIXEL_SIZE;
vec2 steps = texure_size/pixel_step;
vec2 uv = floor(UV * steps) / (steps - 1.0);
COLOR = texture(TEXTURE, uv);
From here on, we will only worry about transparency. I will be setting COLOR.a
(using discard
is also an option, since we will not have partial transparency).
OK, so far it grows, and it has the mosaic effect. However, the elements of the mosaic grow, and there are no visible gaps between them.
I'm going to explain how to keep the size of the particles the same...
First we need to figure out a coordinate system inside of the particle. So we start with the fraction of the UV
we didn't use when we rounded it:
// PARTICLE SIZE
vec2 fract_uv = fract(UV * steps);
Here is where we use UV * steps
again.
This has a couple problems: It also grows, and it is in the regular orientation. We will handle the orientation first...
So, we figure out the direction from the center of the texture:
// PARTICLE SIZE
vec2 fract_uv = fract(UV * steps);
vec2 direction = UV - 0.5;
By substracting UV - 0.5
we have the UV
coordinates shifted, so the origin is at the center (since UV
goes from (0, 0)
to (1, 1)
, UV - 0.5
goes from (-0.5, -0.5)
to (0.5, 0.5)
).
We do not need to normalize it... Instead we will quantize it. That is, we will "round" the direction to a few possible ones, in this case I want the directions we get by simply getting the sign
of the components:
// PARTICLE SIZE
vec2 fract_uv = fract(UV * steps);
vec2 direction = UV - 0.5;
From this I expect four directions:
- Top Left:
(-1.0, -1.0)
- Top Right:
(1.0, -1.0)
- Bottom Left:
(-1.0, 1.0)
- Bottom Right:
(-1.0, 1.0)
That is: I expect the diagonals. I do not expect straight up, down, left and right, unless you have set a pixel_step
and texure_size
that result in an odd number of particles so that there are particles aligned with the center of the texture.
Now, I figure out an angle...
// PARTICLE SIZE
vec2 fract_uv = fract(UV * steps);
vec2 direction = UV - 0.5;
vec2 quantized_direction = sign(direction);
float angle = PI * 1.25 - atan(quantized_direction.y, quantized_direction.x);
Here atan(quantized_direction.y, quantized_direction.x)
is the angle of the quantized_direction
measured from the positive horizontal axis (this is the standard)… Except I want the angle for the Top Left corner to be zero (that part is already oriented how I wanted it), and, well, It might make more sense with the next steps: I rotate the fract_uv
:
// PARTICLE SIZE
vec2 fract_uv = fract(UV * steps);
vec2 direction = UV - 0.5;
vec2 quantized_direction = sign(direction);
float angle = PI * 1.25 - atan(quantized_direction.y, quantized_direction.x);
float angle_sin = sin(angle);
float angle_cos = cos(angle);
mat2 rotation_matrix = mat2(vec2(angle_cos, angle_sin), vec2(-angle_sin, angle_cos));
vec2 rotated_fract_uv = (rotation_matrix * (fract_uv - 0.5)) + 0.5;
As you can figure out, I made a rotation matrix from the computed angle. The idea here is to get a rotated version of fract_uv
, such that the zero is on the outer corner of the particle.
Now, I need to take the difference with PI * 1.25
(225º) so the top left has a rotation of zero. And - via trial an error - I figured out that I need to do PI * 1.25
minus the angle, and not the other way around.
I do substract 0.5
before rotating, and add it again after the rotation, so the rotation happens around the center of the particle.
Next I need to do this to keep the particles of the same size:
float step_position = max(rotated_fract_uv.x, rotated_fract_uv.y);
float inverse_sprite_scale = 1.0 / mix(sprite_scale_begin, sprite_scale_end, progress);
COLOR.a *= step(step_position, inverse_sprite_scale);
Here step_position
is a measure of where on the particle the current fragment is... measured from the outer corner (which was the point of all the rotation stuff). And - again via trial and error - I figured that comparing it with inverse_sprite_scale
allows me to cut the particle so it retains its original size (the size it had when the sprite scale was 1.0
).
Ah, but in your mock up animation I notice the particles shrink! So to add that we have two more uniform
s:
uniform float particle_scale_begin : hint_range(0.0, 5.0) = 1.0;
uniform float particle_scale_end : hint_range(0.0, 5.0) = 1.0;
And we do this instead:
// PARTICLE SIZE
vec2 fract_uv = fract(UV * steps);
vec2 direction = UV - 0.5;
vec2 quantized_direction = sign(direction);
float angle = PI * 1.25 - atan(quantized_direction.y, quantized_direction.x);
float angle_sin = sin(angle);
float angle_cos = cos(angle);
mat2 rotation_matrix = mat2(vec2(angle_cos, angle_sin), vec2(-angle_sin, angle_cos));
vec2 rotated_fract_uv = (rotation_matrix * (fract_uv - 0.5)) + 0.5;
float inverse_particle_scale = 1.0 / mix(particle_scale_begin, particle_scale_end, progress);
if (isinf(inverse_particle_scale)){
COLOR.a = 0.0;
}
else
{
float step_position = max(rotated_fract_uv.x, rotated_fract_uv.y) * inverse_particle_scale;
float inverse_sprite_scale = 1.0 / mix(sprite_scale_begin, sprite_scale_end, progress);
COLOR.a *= step(step_position, inverse_sprite_scale);
}
The reason I'm checking for an infinite inverse_particle_scale
is because you might have set the particle scale to zero (so they shrink to nothing), which result in a division by zero.
Otherwise, what I do to scale the particles is scale step_position
by inverse_particle_scale
.
Finally, you had a dissolve effect which uses a texture, so we have a texture uniform
:
uniform sampler2D dissolve_texture;
And the code is basically what you had:
// DISSOLVE EFFECT
float dissolve = texture(dissolve_texture, uv).x;
dissolve = step(progress, dissolve);
COLOR.a *= dissolve;
Hopefully this is the effect you want. Barring that, I hope I gave you the tools to figure it out.
tex_explode
to accomplish the top animation. However, you have gone a path that usestex_explode
. Thus, I'm guessing that top animation is not actually what you want. What effect do you want to accomplish? If you do actually want the top animation, then what istex_explode
for? \$\endgroup\$