Hello everyone. I’m quite a beginner at shaders and I’m facing a problem.
I want to produce a shock wave (2D) that travels through the map and passes through objects. But how do I manage A to detect the objects and B to pass them smoothly without clipping. Example image:
As you can see, the shock wave can be seen both on and behind the object at the same time
The shader code currently looks like this (simple Wave):
But isn’t there a way to check whether the shader’s pixels overlap with one of the object? So you could then check whether the distance between the pixel position X, Y and the origin of the object has a certain value and then decide whether the pixel is drawn by the shader or becomes transparent.
I just need to be able to check whether UV overlaps with Sprite2D.
If I can’t check that, then the shader is of no use to me, because otherwise I can’t really check whether or when the object was hit
The only problem is that the shock wave spreads, so it gets bigger and bigger. So that the image quality of the wave doesn’t deteriorate, I have to draw it dynamically. I read that CollisionShapes can’t/shouldn’t be scaled.
The shader runs on a large image that covers the entire map
There’s something contradictory in the language you use. You say you want the wave to pass the object without clipping. In the gif provided, the wave is clipping the square. If you don’t want them clipping you have to draw one first, then the other, done. If you want them to clip like in the gif, you need a depth calculation/buffer, even if it’s fake.
Please, clarify what you want and you are missing.
I am sorry, I am from Germany.
I want it exactly as shown in the GIF.
I have already managed to check whether the object is hit by the wave. In GDScript I know where the origin of the shock wave is and also the current radius, so I can calculate whether the origin of the object is within the circle.
Pretty complicated, actually I just want to send a wave from an enemy that damages players/buildings within a certain radius.
Otherwise, I’ll probably have to stick with simpler projectiles and directed “shockwaves” that consist of simple Areas2D and sprites.
If you just want to know the height of the object, you can use size.y, then interpolate that value to find the cutoff point you want. I am not sure where is the difficulty you’re finding.
The difficulty here, i imagine, is splitting the player character into two so that the shockwave can draw inbetween. If this is 2D normally it’s either one or the other, you can’t easily intersect sprites with one another.
Id recommend just drawing the shockwave under the feet
Another option would be with some advanced shader magic. Basically you would need two things:
The shockwave in the background. This would render behind all other objects and does not need any special logic for when to draw or not draw.
The shochwave in the foreground. This is a shader attached to every individual canvas item (like sprites) that can be in front of the shockwave and should intersect with it. This shader uses an emulated/fake 3D height to determine if the shockwave should be drawn or the canvas item should be drawn.
Here is an example how this can look (this is pure 2D):
The shader I used on the sprites:
shader_type canvas_item;
// This defines the shockwave and can be animated
// (the video shows an animated shockwave_radius)
uniform vec2 shockwave_center = vec2(300, 300);
uniform float shockwave_height = 35.0;
uniform float shockwave_radius = 100.0;
uniform float shockwave_width = 30.0;
uniform vec4 shockwave_color = vec4(0.996, 0.667, 1.0, 1.0);
varying vec2 world_position;
varying float emulated_height;
// Calculates the distance to the shockwave circle using
// the center c, radius r and a reference position p
float circle_distance(vec2 c, float r, vec2 p) {
vec2 v = p - c;
float magV = sqrt(v.x * v.x + v.y * v.y);
vec2 a = c + v / magV * r;
return distance(a, p);
}
void vertex() {
// Calculate world position as the shockwave is in global space
world_position = (MODEL_MATRIX * vec4(VERTEX, 0.0, 1.0)).xy;
// Fake height where bottom edge of sprite has height 0
// and top edge has the height of the texture
float texture_height = 1.0 / TEXTURE_PIXEL_SIZE.y;
emulated_height = (1.0 - UV.y) * texture_height;
}
void fragment() {
// Distance to the shockwave and height of the shockwave at that point
// (assuming the circle is a 3D torus, so we use the circle function)
float dist = circle_distance(shockwave_center, shockwave_radius, world_position);
float torus_height = sqrt(pow(shockwave_width * 0.5, 2.0) - pow(dist, 2.0));
// Check if the fragment is inside the 2d circle and lower than the 3d height
// (then combined into should_render if both are true)
float is_inside = 1.0 - step(shockwave_width * 0.5, dist);
float is_lower = 1.0 - step(torus_height + shockwave_height, emulated_height);
float should_render = min(is_lower, is_inside);
// Show the image or render the shockwave according to checks above
vec4 image = texture(TEXTURE, UV);
COLOR = mix(image, shockwave_color, should_render);
}
At the moment the one wave is enough for me, but just out of interest, for several waves you would simply have to copy the fragment code for each additional wave and if waves overlap, just draw the wave that is highest, right?
Okay, but if you rotate the sprites, then it doesn’t take the bottom edge as the height but still the image height.
Is there a simple function to get the lowest point or do you have to calculate that based on the rotation and width/heigth value?
@winston-yallow
Sorry, but I just can’t get it right… I’m trying to give the shader a tilt value (e.g. 10°) and then have the shader adjust the emulated height using this tilt.
For example, if the shock wave comes from below, it first hits the bottom corner and only disappears upwards later.
Could you please tell me how I can do this if it’s not too complicated, otherwise I’ll only try to show objects from the front view.
The sprite is not rotated, so the value is passed to the shader by the script.