Water reflection effect, is it possible?

Then I’d wager that the shader probably isn’t the problem.

Running a snapshot is slower than just drawing to the screen (as Zelda demo shows because it has no shader), and the high quality platformer runs so many full screen tile layers it is absurd, which is the real slow down I’d wager :slight_smile:
I’d like to thank Lerg too for convincing me to not be so uptight about my framerates, which made the whole platformer run quicker.

OK so I finally got around to looking into this, and I’ve come up with the crudest reflection possible.
I’ll calculate the water height on screen outside the routine and pass it with waterLevel, so that it sticks with the level as it scrolls around.

So far I haven’t been able to get it running on my phone, I imagine it is too old, but at least in the simulator it looks really nice :smiley:

The reflection ripples left and right and up and down with false perspective, and fades to transparent the closer you get to the bottom of the screen.

Beneath that you can see what is underwater which currently doesn’t ripple (through laziness, but then I realised it helps differentiate from the reflection), but does fade in as you get further down.

It does look nice.

Any luck if you preface your fragment kernel with this?

#ifdef GL\_ES precision mediump float; #endif

I’ll put that in the code shortly and let you know when I’ve uploaded it. So the code you offer should go right at the top of the shader file, before even the local kernel = {} or where?

When you say old, how old? My phone is pretty old, it is a Samsung S2, and it still manages all the parallax and shader effects at almost 60 fps. Obviously though my phone was top of the range in its day, so I imagine it has more to do with feature set than age as such.

Top of the string you assign to kernel.fragment.

Err, you said old.  :P I think this is less an issue of age than OpenGLES being very anal about fragment precision. This very thing stumped me for a while, coming mostly from desktop-based HLSL.

Doh! Heh major confusion there I must admit. No, the problem turned out to be rather simple (when you know). I was doing some math on the sin() and cos() values, and one of the numbers I wrote was an integer. Putting . after it to force to float was fine.

One thing I’m noticing now is that the longer I leave my app running, the crapper and jerkier the water becomes, although the app itself runs at the same frame rate as before. Let me post my shader, you might spot something that could be an issue.

Here’s a link to a working APK: https://www.dropbox.com/s/uvj2881rrbo1jyg/platformer.apk?dl=0

local kernel = {} &nbsp; kernel.language &nbsp; &nbsp; &nbsp; &nbsp;= "glsl" kernel.category &nbsp; &nbsp; &nbsp; &nbsp;= "filter" kernel.name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;= "water" kernel.isTimeDependent = true &nbsp; -- Expose effect parameters using vertex data kernel.vertexData = { &nbsp; &nbsp; { &nbsp; &nbsp; &nbsp; &nbsp; name &nbsp; &nbsp;= "waterLevel", -- The property name exposed to Lua &nbsp; &nbsp; &nbsp; &nbsp; default = 0.6,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; min &nbsp; &nbsp; = 0, &nbsp; &nbsp; &nbsp; &nbsp; max &nbsp; &nbsp; = 1, &nbsp; &nbsp; &nbsp; &nbsp; index &nbsp; = 0, -- This corresponds to CoronaVertexUserData.x &nbsp; &nbsp; }, } &nbsp; kernel.fragment = &nbsp; [[#ifdef GL\_ES &nbsp; precision mediump float; #endif &nbsp; P\_COLOR vec4 FragmentKernel( P\_UV vec2 texCoord ) { &nbsp; &nbsp; P\_RANDOM float mirrorY &nbsp;= CoronaVertexUserData.x; &nbsp; &nbsp; P\_COLOR vec4 baseTexCol = texture2D( CoronaSampler0, texCoord ); &nbsp; &nbsp; P\_COLOR vec4 reflectionTexCol; &nbsp; &nbsp; P\_RANDOM float ratio, xOffset, yOffset; &nbsp; &nbsp; if ( mirrorY \>= 0.5 ) { &nbsp; &nbsp; &nbsp; &nbsp; if ( texCoord.y \> mirrorY ) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ratio &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = texCoord.y - mirrorY; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; xOffset &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = cos( CoronaTotalTime \* -8. + ratio \* 100. ) \* 0.03 \* ratio; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; yOffset &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = sin( ( CoronaTotalTime \* -10. + ratio \* 100. ) ) \* 0.05 \* ratio; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reflectionTexCol &nbsp; &nbsp; &nbsp;= texture2D( CoronaSampler0, vec2( texCoord.x + xOffset, mirrorY - ratio - yOffset &nbsp;) ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reflectionTexCol.xy &nbsp;\*= ( 1.3 - ratio \* 4. ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reflectionTexCol.z &nbsp; \*= ( 1.5 - ratio \* 5. ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; baseTexCol.xy &nbsp; &nbsp; &nbsp; &nbsp; \*= ratio + 0.01; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; baseTexCol.z &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\*= ( ratio \* 4. + 0.02 ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; baseTexCol.xyz &nbsp; &nbsp; &nbsp; &nbsp; = baseTexCol.xyz + reflectionTexCol.xyz; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; xOffset = cos( CoronaTotalTime \* -2. + texCoord.y \* 60. ) \* 0.003 + cos( CoronaTotalTime \* -2.3 + texCoord.y \* 5. ) \* 0.003; &nbsp; &nbsp; &nbsp; &nbsp; yOffset = sin( CoronaTotalTime \* -3. + texCoord.x \* 30. ) \* 0.003 + sin( CoronaTotalTime \* -2.7 + texCoord.x \* 7. ) \* 0.003; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; baseTexCol = texture2D( CoronaSampler0, vec2( texCoord.x + xOffset, texCoord.y + yOffset ) ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if ( texCoord.y \< mirrorY ) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ratio &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = mirrorY - texCoord.y; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; xOffset &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = cos( CoronaTotalTime \* -8. + ratio \* 100. ) \* 0.03 \* ratio; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; yOffset &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = sin( ( CoronaTotalTime \* -10. + ratio \* 100. ) ) \* 0.05 \* ratio; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reflectionTexCol &nbsp; &nbsp; &nbsp;= texture2D( CoronaSampler0, vec2( texCoord.x + xOffset, mirrorY + ratio - yOffset ) ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reflectionTexCol.xy &nbsp;\*= ( 1.3 - ratio \* 4. ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reflectionTexCol.z &nbsp; \*= ( 1.5 - ratio \* 5. ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; baseTexCol.xy &nbsp; &nbsp; &nbsp; &nbsp; \*= ratio + 0.01; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; baseTexCol.z &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\*= ( ratio \* 4. + 0.02 ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; baseTexCol.xyz &nbsp; &nbsp; &nbsp; &nbsp; = baseTexCol.xyz + reflectionTexCol.xyz; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; baseTexCol.xy -= 0.2; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; return CoronaColorScale( baseTexCol ); }]] &nbsp; return kernel

Ah, the number thing has been biting me A LOT. I wonder if there’s a #pragma or something to suppress it.

I’ll check the APK when I’m home. The playground version looks really nice! All the little tuning factors really bring some life to it.

If I had to guess, I’d say it’s something like this:

(1 - After CoronaTotalTime gets large enough, your numbers are overflowing the representable value of a mediump. If these are IEEE-style floats, they may start spacing differently once they pass 1023: 1024, 1026, …, then later by 4, 8, etc. until they 2^14 and then becoming… something. Modded by 1024? Junk? (Junk would probably be fairly obvious.) I’m not sure.

If you do have a fancier device on hand, you could change that earlier snippet to

#ifdef GL\_ES #ifdef GL\_FRAGMENT\_PRECISION\_HIGH precision highp float; #else precision mediump float; #endif #endif

and see if the issue disappears.

(2 - Along those same lines, if your scale factors are close to, but not identical with one another, some of them may overflow while others don’t, and give you weird boundary effects in your graphics. This happens in all my noise shaders.  :frowning: (And is behind certain of Corona’s built-in effects’ requirements for high precision, I assume.)

I’m not sure there is a general solution to this problem.

However!

In this case you may actually be in luck, since the trigonometric functions are all periodic. So now that you’ve worked it all out on the fragment side, you could move the angle computations over into the vertex kernel. This already gives you back high precision availability. But more relevant to the problem at hand, you could sanitize the angles by modding them by some multiple of 2 * pi and passing those, through varyings, on to the fragment side. (If you cross a 2 * pi -> 0 branch you’ll need to correct for that, of course.)

I’ve updated my demo and shader at the link above. More watery effects and I think it is nicer looking than before. Apparently I’m happy wasting hours tweaking shaders now. Curse you Corona!

I’ve moved from using the corona time variable to a user-defined one, so the user can control the water speed, pause it or rewind.

I can’t % the value though (at least not in a seamless fashion) because all the sin and cos functions deliberately use non-repeating values. I think all I’ll do is find at what value it roughly goes wrong, and in my game code mod my ‘time’ variable to this. It’ll create a 1 frame jerk, but hopefully it’ll be far enough apart so as to not really be noticable :frowning:

I should also point out I have no idea what you are talking about moving the angle computations into the vertex kernel. This is my first attempt at messing with shaders, so I’m really just flying blind!

Oh, sorry, “angles” as in “arguments to cosine and sine”, since they’re defined on the unit circle and all (versus the sine wave way of thinking about them). But that’s the thing, the arguments themselves loop every multiple of 2 * pi: 30 degrees and 390 degrees are identical, from their point of view. So you can sanitize potentially huge time-based values down to that range (-7 < x < 7), where you shouldn’t even need to think about being able to add and multiply them together.

And yes, shaders can become a bit addictive. Then you become “the shader guy” at your office and never get a break.

Well yes, but each sin or cos has a different multiplier from time, and is (normally) a combination of more than 1 variable, precisely so they aren’t in the same phase. Have a look at my shader code above. It may be that you are right and I just don’t understand you :slight_smile:

Oh, right. I actually meant doing it in each case.  :slight_smile:

So you would add a vertex kernel (where high precision is always available), mod CoronaTotalTime * -8., CoronaTotalTime * -10., etc., and stuff those into varyings, which GLES will then feed (interpolating them, too) to the fragment kernel.

Adding two modded numbers (and then modding the sum, but cos () and sin () do that for us) is the same as adding them and then modding the sum, so you could still just do things like you are, e.g. cos(MyVarying.x + ratio * 100.). The end result is the same, just without the potential for overflow.

Also worth noting is that you can package values into a vec2 , vec3 , or vec4 and call  cos () or sin () on that, in order to calculate multiple values in parallel. This is true of most of GLSL’s built-in functions.

You are gonna have to show me how to pass these values between things. Is this all with the intention of pre-calculating values for an entire pass, or does it also happily work for per-pixel values (every sin() and cos() changes per fragment).

Sure.

You’d have a vertex kernel like (untested):

kernel.vertex = [[varying P\_POSITION vec4 TrigArgs1; // Add these same declarations before FragmentKernel varying P\_POSITION vec2 TrigArgs2; float PI = 4. \* atan(1.); float TWO\_PI = 2. \* PI; P\_POSITION vec2 VertexKernel (P\_POSITION vec2 position) { TrigArgs1 = mod(vec4(-2., -3., -8., -10.) \* CoronaTotalTime, TWO\_PI); TrigArgs2 = mod(vec2(-2.3, -2.7) \* CoronaTotalTime, TWO\_PI); return position; }]]

As the comment says, add those same variables in the fragment kernel and use their values in the appropriate places.

(GLES guarantees at least eight varyings. Corona must commandeer one for the texture coordinates, but you should still have quite a few.)

These are assigned per-vertex (in the kernel), though they get interpolated per-pixel. Of course, in this case they all get interpolated to the same values.  :slight_smile: The interpolation more or less assumes linearly valued data, though it does perform perspective correction (as you’ve discovered with texturing and rect paths). In theory it’s faster to do the computations in the vertex shader (when it makes sense), on account of the vertex-to-pixel frequency and certain batching operations, but in this case it’s just (an attempt) to fix the choppiness.

Ok thanks, I think I understand it better now, much appreciated.

I (finally!) gave this a whirl just now. It looks really nice! Unfortunately, my poor Xoom is brought to its knees.  :slight_smile:

In that same scene, there are occasional horizontal seams in the leaves. Is that a known issue? Is there an effect on those?

Does it run poor in the non-shader version too, just wondering if it is actually the shader causing the problem or not.

As for the seams, I left them in for laziness, but it also proved useful as Lerg and I had a chat about how best to remove seams between tiles (for example, the common way I’ve mentioned, expanding the edge pixels by 1, is not actually always the *best* way - it does remove the seams, but it doesn’t look the nicest).

The two “snapshot” platformer scenes are slow; the others seem fine.

The Zelda scene takes a slight hit in snapshot mode, too, but is quite playable anyway.

Then I’d wager that the shader probably isn’t the problem.

Running a snapshot is slower than just drawing to the screen (as Zelda demo shows because it has no shader), and the high quality platformer runs so many full screen tile layers it is absurd, which is the real slow down I’d wager :slight_smile:
I’d like to thank Lerg too for convincing me to not be so uptight about my framerates, which made the whole platformer run quicker.