Shaders (water)reflection for irregular shapes (shaders)

Gamer-15

Member
Hi,

I am trying to make reflections of these landscape on the water.

The problem is that the shapes have a non straight coastline (red line in figure 2 below). If it was a straight coast line the sprite could just be flipped around the x-axis. But if done on these sprites it causes bad results as shown below. I was thinking about flipping it in a shader. But I have not a clue how this could be done.

What is known is this: the coastline (reflection_y) for a given x is at the transition from pixels with alpha>0 to pixels with alpha is 0 (when increasing y). When this reflection y for given x is known, the colors of the pixels below could be calculated. But I have no idea how to implement this into shader code...

Any help is very much appreciated.


Asset 280.png

Asset 281.png
 

obscene

Member
Basically, since this is not 3D, there is no way to correctly reflect that scene with one simple effect. If this scene you have is the entire thing to be reflected, I would just make custom sprites for each element of it. But if this system needs to be dynamic and accommodate a wide array of shapes then you're going to have a tough time with it. If you are planning on using some distortion or blur effect that would mask a lot of the imperfections you could probably just get away with a simple mirroring of the sprite with the origin set a few pixels from the bottom. Really it just depends on what you want the final image to be and how versatile you need it.
 

muki

Member
For this scene, from this angle, best to draw custom reflection sprites for the elements you want reflected. With the corrected outlines "drawn in".

There's no real way to generate (via shader) an accurate reflection from irregular 2D shapes from this angle, unless you are a master at shader programming I think. There might be a way to 'shift' columns of pixels on the reflection up to meet the coastline pixels, but I wouldn't even know how to begin to go about it. And even that would be a hack (ie it might make some of the bumps on the mountains look totally wrong when reflected, it might turn a bump into a crater, sort of thing).

An alternative is to recreate all of this in 3D, and render it in 3D via GMS with simple materials, fog and a reflection shader, even if the rest of the game is 2D. That's probably much more work than the first option of drawing custom reflection sprites, unless you are already comfortable with 3D programming/modeling.
 
Last edited:

SoapSud39

Member
I can think of a some relatively simple shader code that will create a sort of reflection which will accommodate different shapes and skews in 2D. I didn't really want to do this when I saw your post earlier because it checks a lot of pixels, but I'll outline it here.

In the object: get/set uniform float for texture texel height (this will translate to a pixel's height for use in the shader code)
In shader:
  • establish uniform float for pixel height
  • establish float for alpha (equal to base texture alpha)
  • establish float for vertical pixel offset
  • if alpha is 0 (transparent), check upward
  • establish float for alpha at position plus vertical offset
  • if alpha at vertical offset is 0, increase vertical offset (write a for loop, up to a certain max distance)
  • in for loop, if alpha at vertical offset is not 0, establish float identical to current vertical offset value (establish it to be something like -1.0 first), break for loop; if vertical offset is greater than max, just break for loop
  • after breaking for loop, if offset is not -1, check alpha at vertical offset * 2 --> if it is not 0, set color at texture coordinates to desired color and alpha (1.0, or equal to alpha at offset)
    • vertical offset * 2 because the while loop stops at distance from current pixel to base of object, and you want to check if that distance allows for reflection
    • if you want a fade effect you can set the alpha to be lower based on the offset distance
  • remember to set gl_FragColor.rgba to draw the reflection
Notes:
  • To ensure that the reflection is actually drawn, make an appropriate amount of space in your sprite and turn off 'automatically crop' on texture page settings. Alternatively, to get that blank space, you can draw the object onto a surface (make sure the surface has enough space below for a reflection) and then use the texture of the surface for the texel height and draw the surface.
  • You can get a wavy effect with some sine or cosine functions and uniforms
  • if you do it right the shader will work for any shape, just that reflected curves become flatter
  • if you'd like, give me a reply and I can write up some shader code later for you to test
 

Gamer-15

Member
Thanks All!

So I have around 40 landscapes with water, that is why I prefer shader code. And drawing the reflections by hand to really mirror it isn't always that easy. I tried doing some 3d stuff with the landscapes/water, but it looked way better in 2d when I tried it.

SoapSud39
Thanks, that is a great solution!
Here is my attempt to implement it:

Fragment Shader:
GML:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform float texel_h;

void main()
{
float y_offset_applied=0.0;
if (texture2D(gm_BaseTexture, vec2(v_vTexcoord.x,v_vTexcoord.y)).a==0.0)//current pixel
{
  for (float y_offset=0.0;y_offset<1.0;y_offset+=texel_h)
  {
   if (texture2D(gm_BaseTexture, vec2(v_vTexcoord.x,v_vTexcoord.y-y_offset)).a>0.0)//checking upwards
   {
    y_offset_applied=y_offset;
    break;
   }
  }
}
gl_FragColor = texture2D(gm_BaseTexture, vec2(v_vTexcoord.x,v_vTexcoord.y-y_offset_applied*2.0));
if (y_offset_applied>0.0)
gl_FragColor = vec4(texture2D(gm_BaseTexture, vec2(v_vTexcoord.x,v_vTexcoord.y-y_offset_applied*2.0)).rgb,texture2D(gm_BaseTexture, vec2(v_vTexcoord.x,v_vTexcoord.y-y_offset_applied*2.0)).a*0.5);
}
Draw event:
GML:
texel_uni=shader_get_uniform(reflection_shader,"texel_h");
shader_set(reflection_shader);
shader_set_uniform_f(texel_uni,texture_get_texel_height(sprite_get_texture(spr_island1, 0)));
draw_sprite_ext(spr_island1,0,150,130,1,1,0,c_white,1);
shader_reset();
image1028.png



So it works, but it looks pixelated at the edges when the bottom is not a flat line (right one in screenshot). Any idea how the shader code can be improved for this?
 
Last edited:

xenoargh

Member
What you're seeing there is due to aliasing on the source image. The shader's working properly. To improve the final result where it's pixelated, run the final result through a quick blur-pass, sampling vs. alpha of nearby pixels, then average. Or don't alias the edges when you make the sprites in your image editor.
 

Gamer-15

Member
I tried with a sprite without anti aliasing in below screenshots. It still has the problem. Also the left island at my previous post (which has anti aliasing) has no issues. It only occurs when the bottom is not a flatline.

No anti aliasing:
Asset 289.png

Close up detail:
image1049.png

Edit: Ah, now I measured the distances from the coastline and see that the pixilation/bumps appear as they should using this method. I will try to implement the blur-pass to smooth it out.
 
Last edited:
Top