Sprite Water Reflection Pixel Art Pokémon Style
Shader that simulates the Pokémon Gen 3 water reflection effect.
It is ready to use with sprite sheets textures. You can modify the movement options and the color.
It is my first shader in Godot, any feedback is welcome.
Edit:
[2023-10-24]
* Fixed problem with displacementPixels
different to 1.
Shader code
shader_type canvas_item;
uniform int hFrames = 1; //Number of horizontal frames in the Texture
uniform int vFrames = 1; //Number of vertical frames in the Texture
group_uniforms Displacement;
uniform bool enableMovement = true;
uniform int displacementPixels = 1; // Number of pixels to displace
uniform float timeGap : hint_range(0.0, 1.0, 0.01) = 0.4; // Time in seconds that Left side movement will wait to move
uniform float speed = 1.25; // Movement speed
/*
SideRelationTime determines the time ratio spent in each step.
For example, with a value of 0.25, a quarter of the time is spent in the normal position,
and the remaining three-quarters in the shifted position.
*/
uniform float leftSideRelationTime : hint_range(0.0, 1.0, 0.01) = 0.3;
uniform float rightSideRelationTime : hint_range(0.0, 1.0, 0.01) = 0.3;
group_uniforms Color;
uniform vec3 color: source_color = vec3(1.0);
uniform float colorMix: hint_range(0.0, 1.0, 0.01) = 0.0;
uniform float alpha: hint_range(0.0, 1.0, 0.01) = 1.0;
float ShiftPixels(float uvX, float normalizedUvX, float shift) {
if (normalizedUvX <= 0.5) {
//left side
uvX -= shift * step(mod((TIME + timeGap) * speed, 1.0), leftSideRelationTime);
}
else {
//right side
uvX -= shift * step(mod(TIME * speed, 1.0), rightSideRelationTime);
}
return uvX;
}
// We have to resize the sprite so it is not cut
void vertex(){
switch (VERTEX_ID){
case 0:
VERTEX.x -= float(displacementPixels);
break;
case 1:
VERTEX.x -= float(displacementPixels);
break;
case 2:
VERTEX.x += float(displacementPixels);
break;
case 3:
VERTEX.x += float(displacementPixels);
break;
}
}
vec2 AdjustUvToNewSize(vec2 originalUV, float texturePixelSizeX, vec2 numberFrames, float frameX) {
// relation between the previous size and the new one
float relation = (1.0 + 2.0 * texturePixelSizeX * float(displacementPixels) * numberFrames.x);
vec2 uv = originalUV;
uv.x *= relation;
// adapt new position to new uv
//uv.x -= float(displacementPixels) * texturePixelSizeX;
// displaces the pixels depending on the sprite column
//uv.x -= float(displacementPixels) * frameX * numberFrames.x * texturePixelSizeX / 2.0;
// optimized operation
uv.x -= float(displacementPixels) * texturePixelSizeX * (1.0 + frameX * numberFrames.x / 2.0);
return uv;
}
void fragment() {
vec2 numberFrames = vec2(float(hFrames), float(vFrames));
vec2 normalizedUv = mod(UV * numberFrames, vec2(1.0));
float frameX = floor(UV.x * numberFrames.x);
vec2 uv = AdjustUvToNewSize(UV, TEXTURE_PIXEL_SIZE.x, numberFrames, frameX);
if (enableMovement) {
uv.x = ShiftPixels(uv.x, normalizedUv.x, TEXTURE_PIXEL_SIZE.x * float(displacementPixels));
}
// discard pixels that are out of the sprite
if (uv.x <= frameX * 1.0 / numberFrames.x || uv.x >= 1.0 / numberFrames.x * (frameX + 1.0)) {
discard;
}
COLOR = texture(TEXTURE, uv);
COLOR.rgb = mix(COLOR.rgb, color, colorMix);
COLOR.a *= alpha;
}
This looks great, but I can’t figure out how to implement it. Do you have a tutorial written up anywhere?
Hi, sorry for the late reply. I have updated the page with a link to a project I have just created with a simple demo. Let me know if you have any question!
Hi, nice work, how can i make the reflection shows only on water tiles and it cuts the reflection on corners??
Hi, the solution I have implemented is simply to have the water and earth tiles in separate layers and have different z index for each thing. The idea is that the order of painting is: Water -> Reflection -> Land -> Player. Depending on your implementation you might want to look for a more complex system using masks or shaders. I hope that helps.