Slice – 2D pixel perfect slice shader
Creates two halves of a texture and allows you to move ’em. You can change the orgin of the angle of the slice, the angle of the slice, the pixel size of your image, and the distance from the center of each slice.
Shader code
shader_type canvas_item;
// Center of the slice, normalized [0,1]
uniform vec2 slice_center = vec2(0.5, 0.5);
// Angle of the slice in radians
uniform float slice_angle = 0.0;
// Distance to move the slices apart, normalized [0,1]
uniform float slice_distance = 0.0;
// Pixel size for pixel-perfect rendering
uniform float pixel_size = 1.0;
// Expansion factor for the sprite bounds
uniform float expansion_factor = 0.5;
// Border color for the expanded area
uniform vec4 border_color = vec4(0.0, 0.0, 0.0, 0.0);
// Convert UV to pixel-perfect UV
vec2 pixel_uv(vec2 uv, float pixel_sizee) {
return floor(uv * pixel_sizee) / pixel_sizee;
}
// Rotate a point around another point
vec2 rotate(vec2 point, vec2 center, float angle) {
vec2 translated_point = point - center;
float sin_angle = sin(angle);
float cos_angle = cos(angle);
vec2 rotated_point = vec2(
translated_point.x * cos_angle - translated_point.y * sin_angle,
translated_point.x * sin_angle + translated_point.y * cos_angle
);
return rotated_point + center;
}
void vertex() {
// Expand the vertex positions and UVs
VERTEX *= (1.0 + expansion_factor * 2.0);
UV = (UV - 0.5) * (1.0 + expansion_factor * 2.0) + 0.5;
}
void fragment() {
vec2 uv = UV;
// Adjust UV to handle pixel-perfect rendering
uv = pixel_uv(uv, pixel_size);
// Calculate the vector from the slice center to the current UV
vec2 offset = uv - slice_center;
// Rotate the offset vector by the negative slice angle
vec2 rotated_offset = rotate(offset, vec2(0.0), -slice_angle);
// Determine which side of the slice the current UV is on
bool is_top = rotated_offset.y > 0.0;
// Move the UV based on the slice distance
vec2 movement = vec2(
slice_distance * cos(slice_angle),
slice_distance * sin(slice_angle)
);
if (is_top) {
uv += movement;
} else {
uv -= movement;
}
// Check if UV is outside the [0, 1] range and set the border color if it is
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
COLOR = border_color;
} else {
// Sample the texture with the modified UV
COLOR = texture(TEXTURE, uv);
}
}
Damn, the gif looks AMAZING but I can’t get it to work at all.
As soon as I apply the shader to anything, my entire texture just disappears.
And if I set a border color, I just get a large square of that color covering the entirety of my node (+ more).
Looking at the code I think it means the shader thinks every single pixel is outside of the [0,1] range so it just draw the border everywhere… but I don’t understand why.
Even if I just create a new project and add a sprite then add the shader, I get the issue.
Which Godot version are you using? (4.2 stable here)
Edit : Ah, I got it. I was confused by the Pixel Size parameter. For anyone else with the same issue : you gotta input the amount of pixels in your texture, basically.
So if your texture is 32x16px, for example, then set Pixel Size to 512 and the shader will work
Here’s a modified version that allows you to slice everything on screen rather than one texture! If you want to have a boss that slice your whole game in half or something like that 🙂
Create a textured area (eg. TextureRect, ColorRect, etc…, but it over what you want to slice (and with an higher z-index) then add a material to the area with this shader :
(Added a screen sampler, changed UV to SCREEN_UV in fragment, and added Color.rgb = textureLod(screen_texture, uv, 0.0).rgb to the end of fragment)
The shader I posted above is mostly useful if you want to slice the whole screen, because the “slice center” you set in the shader parameters will be based on the center of the camera/viewport, NOT the center of your Texture node. So, it’s useless if you want to slice something that’s not near the center of the screen.
Here’s another version that actually center on your node’s center ( + whatever offset you set in the parameters). Allowing you to slice anything, anywhere. Yay!
I think there’s still a small bug where the angle won’t be the same depending on the viewport aspect ratio (because SCREEN_PIXEL_SIZE and FRAGCOORD use the windows size, including the space outside the viewport. what I’d need is something like VIEWPORT_PIXEL_SIZE and a fragcoord equivalent too, but that doesnt seem to exist).
If anyone have a fix idea, please comment :). But that bug doesn’t seem like a big deal, try it out.
OK, discovered some more bugs afterward…. sadly it seems I can’t edit or delete my previous comment to remove the bugged wall of code :(.
Here’s a Node version where everything works correctly :
NOTE : It seems to ONLY work if you use a Sprite2D. ColorRect or TextureRect won’t expand correctly for some reason.
You can just use the Godot icon as a texture and then scale the Sprite to whatever size you need, it works well.
Final version (?) :
Thank you very much, the modified code is very good, but there are some bugs that I can not use, so I made some modifications.
shader_type canvas_item;
// 切片的中心,规范化到 [0,1] 范围内
uniform vec2 slice_center = vec2(0.5, 0.5);
// 切片的角度,以弧度为单位
uniform float slice_angle = 0.0;
// 切片分开的距离,规范化到 [0,1] 范围内
uniform float slice_distance = 0.0;
// 像素大小,用于像素完美渲染
uniform float pixel_size = 1.0;
// 精灵边界的扩展系数
uniform float expansion_factor = 0.5;
// 扩展区域的边框颜色
uniform vec4 border_color = vec4(0.0, 0.0, 0.0, 0.0);
// 围绕另一个点旋转一个点
vec2 rotate(vec2 point, vec2 center, float angle) {
vec2 translated_point = point – center;
float sin_angle = sin(angle);
float cos_angle = cos(angle);
vec2 rotated_point = vec2(
translated_point.x * cos_angle – translated_point.y * sin_angle,
translated_point.x * sin_angle + translated_point.y * cos_angle
);
return rotated_point + center;
}
void fragment() {
vec2 uv = UV;
// 计算从切片中心到当前 UV 的向量
vec2 offset = uv – slice_center;
// 通过负切片角度旋转偏移向量
vec2 rotated_offset = rotate(offset, vec2(0.0), -slice_angle);
// 确定当前 UV 位于切片的哪一侧
bool is_top = rotated_offset.y > 0.0;
// 根据切片距离移动 UV
vec2 movement = vec2(
slice_distance * cos(slice_angle),
slice_distance * sin(slice_angle)
);
if (is_top) {
uv += movement;
} else {
uv -= movement;
}
// 检查 UV 是否在 [0, 1] 范围之外,如果是则设置边框颜色
if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
COLOR = border_color;
} else {
// 使用修改后的 UV 采样纹理
COLOR = texture(TEXTURE, uv);
}
}
Bro these are 4 posts and the code is 3 times the size of OP and its functionality. It deserves its own shader post here, or if you want to reach more people, a youtube video (no webcam, no voice, just show step by step to setup with text)
If I had a 3D card (so Z is completely non-existent), how would this be converted?