I currently implement a game with a top down view of the ocean. I use the following, a little abstract texture: enter image description here

The actual texture is transparent, I added the green-like color for clarity.

The problem I now have is, that I don't know how to animate this texture so the water looks nice. I tried to move the texture with a sin wave: texture.y += sin(angle). Of course now the whole texture is moving which looks kind of unrealistic. The next thing I tried is to add another layer and implement a parallax effect. So that reflections under the water surface would also move, but much slower. It looks a little better but still not... nice enough.

I think the best looking animation would be, if the individual cells would expand and contract, kind of like a web or piece of cloth. Imagine if someone would slightly pull at one vertex of these cells and the neighbored cell would expand and the cell I pull towards to (or push to) would contract. Kind of like a web of springs(?). But I have no clue how to implement something like this:

  • Whats the math-model for this? Something with springs, where forces push/ pull?
  • And if so, how do I map this model to the given texture? Keeping all the curves and what not...

(I'm also open to different ideas/answers on how to animate the given texture. Realism is not the point here, just some nice looking water like movements...)

Solution from DMGregory

I posted a libgdx example in this post: 2d water animation is jagged and not smooth (see answer about texture filtering)

We're looking for long answers that provide some explanation and context. Don't just give a one-line answer; explain why your answer is right, ideally with citations. Answers that don't include explanations may be removed.

up vote 39 down vote accepted

A common way this is done is using an indirect texture lookup in the shader to distort the display texture:

Animated gif showing water animation

Here I'm using a texture with some low-frequency colour noise (tiling smooth blobs of random colours), and scrolling it across the display geometry over time.

enter image description here

Instead of drawing the colours from this texture, I instead take the red & green channels and subtract 0.5f to turn them into a pseudorandom 2D vector that changes smoothly over time & space.

I can then add a small multiple of this vector to my UV coordinates, before sampling from the main water texture. This shifts the part of the texture we're reading & displaying, warping it around.

By averaging two samples from this noise, scrolling in opposite directions, we can hide the direction of movement so it just looks like aimless sloshing.

In Unity the shader would look like this - it should be simple enough to translate to the shader language of your choice:

fixed4 frag (v2f i) : SV_Target
{               
    float2 waveUV = i.uv * _NoiseScale;
    float2 travel = _NoiseScrollVelocity * _Time.x;

    float2 uv = i.uv;
    uv += _Distortion * (tex2D(_Noise, waveUV + travel).rg - 0.5f);
    waveUV += 0.2f; // Force an offset between the two samples.
    uv += _Distortion * (tex2D(_Noise, waveUV - travel).rg - 0.5f);

    // Sample the main texture from the distorted UV coordinates.
    fixed4 col = tex2D(_MainTex, uv);

    return col;
}
  • 1
    This looks really nice. I'm not sure I understand all the attributes: _NoseScale = scalar for scaling the "noise map". _NoiseScrollVelocity = Vector2 at which speed we move across the noise map. _Noise = ?. _Distortion = Scalar I choose as a distortion factor? v2f = Vertex we determine the color. i = ? – morpheus05 Sep 14 at 12:24
  • _Noise is [a texture sampler that reads from] the little blobby random texture above. v2f i is the interpolated data from the vertex shader — we mainly use it to get the texture coordinates for the pixel we're drawing, i.uv. And you're exactly correct about all the rest. – DMGregory Sep 14 at 12:40
  • I've implemented the shader and somehow it doesn't work (it doesn't move or the distortion is to big), I assume I didn't set the values correctly. time = the difference from the last frame in ms. noise_scale = 1 (I use your texture, wrap mode repeat) noise_scroll_velocity = [0.01, 0.01] distortion = 0.02 – morpheus05 Sep 18 at 11:06
  • Note that the variable is called Time, not DeltaTime. If you use a difference in time, and your framerate is consistent, then you'll always get the same number and you'll be re-running the shader with the same inputs, getting the same output (nothing moves). Worse, if your framerate is inconsistent then you'll get back & forth vibration. You want total time elapsed, not delta time. – DMGregory Sep 18 at 11:09
  • A soon is I hit send I realised that and now it almost works. The animation seems to poduce waves from the bottom right corner and after, 10 or so seconds it just fades out, like waves that stop. What could be the reason for this? – morpheus05 Sep 18 at 12:19

This is called a caustic effect, and generating these effects at runtime is fairly time consuming, so this is traditionally done with pre-rendered frame-by-frame animation. There are tools that will generate caustic animation frames for you, such as Caustics Generator, which has a free version for non-commercial use. There are also some pre-made ones that you can buy for significantly cheaper than the pro version of the tool I mentioned.

Note that caustic effects are also usually an effect applied as a light cookie on an underwater terrain, or on the underwater surface. That is, putting it on the surface of the water while looking down into it is not normally what water looks like.

  • Thats very interesting, I will also have a look at this generator (event though, I will try the shader variant if I understand it...) – morpheus05 Sep 14 at 12:25

This looks like a texture you could generate from a voronoi graph, e.g.:

enter image description here

You can make small, smooth adjustments to the graph by moving the points; re-drawing the graph each frame would be quite expensive, so you'd likely want to pre-render the animation.

There is a oldschool method, involving a bottom texture layer, and two half transparent textures for reflection on top.

If you want to go all the way and want the water to not look full of cloned waves or samish blue soup- flowmaps are the got to goal.

https://steamcdn-a.akamaihd.net/apps/valve/2010/siggraph2010_vlachos_waterflow.pdf

  • 3
    Although links may help, they never make good answers. Could you expand on both of theses methods? How would the go about implementing it? – Alexandre Vaillancourt Sep 14 at 13:56
  • The first method, is basically a very old method used to animate water- you take a base layer water texture whose UVW Coordinates get shifted into the direction of your choice. Now you apply in addition a normal map/ bump-map which you shift into another direction- if done well, this looks convincing for small rivers. Its very limited though for large bodies of water- as anything ressembling waves will get moire effect. The Link explains the use of flowmaps far bettern then i could. – Pica Sep 15 at 22:38
  • Please use the edit feature to improve the question with what you added here :) People are used to look for answers in the post, not in the comments. – Alexandre Vaillancourt Oct 8 at 4:08

protected by Alexandre Vaillancourt Sep 14 at 13:57

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.