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.
Hey Christt105 !
Just discovered your Shader and it works perfectly for AnimatedSprite2D !
Using multiple layers and Z index allow for perfect Pokémon Ruby/Sapphire/Emerald water reflection.
Sadly, i can’t make it work for a simple TimeMap (or tile).
The effect is kinda working but not at intended, no matter the parameters of the Shader.
Do yo have any idea why ?
Here’s the result so far :
https://streamable.com/wsdzv4
The idea would be to apply your Shader to a tile of a Tilemap called “Reflections Layer”, and simply reverse the Y axis. Playing with other layers and Z axis, i’m sure we can pretty much implement the simplest version of Pokémon water reflect.