Running water, early video

Hi, I’m working on a system for running water. This is an early video I made. The system has changed some, but I am too busy with the forester lib now to do better videos etc. I’ll add code and more info later, this will be the official thread for this project.



http://www.youtube.com/watch?v=7iFdfKVtEyM



The system uses simplewaterprocessor, with a flowmap instead of a du/dv map for displacement.

The shader is based on the work of these guys. What I did was I took their openGL shader, made it a bit easier to use, cut out the makeshift reflection/refraction stuff and plugged it into the jME simplewaterprocessor.

The idea behind the shader is fairly simple. Basically it reduces the water to a grid of sample points (like 32x32 or 16x16), which are then blended using a multi-sampling technique. I'll get more into details when I put the code up. Still need to clean it up a little, and add more comments.

I also started working on a system for generating flowmaps from terrain heightmaps btw, that I hope to include some time. The water in the vid uses a flowmap that has been generated, not painted manually. It's very primitive at this point tho.
22 Likes

nice man!

This is really nice androlo, nice work as always!

Looks very nice! All it needs is a bit of turbulence/reflaction/refrection in the waves and water flow at the edges etc and it would be perfect.



I notice you are only getting 30fps though, was that capped down for capture?

n1ce! :smiley:

Wow, this is awesome :slight_smile:

Looks many times better than using the sea- and lake-water system for rivers^^ :slight_smile: Nice to see.

Cool!!

Sweet :slight_smile:

looking awesome :slight_smile:

Wow. It exceeds my expectations :slight_smile: Very nice work @androlo.

Wow, that’s some nice work bro! That looks fantastic. I wonder if it can get more wavy (higher waves).

Yes it can. It’s possible to change everything, amplitude, wavelength etc., and also to vary it over each “tile”. For example, if you look at the vid you’ll notice water runs faster in the deeper parts and slower near the edges. This is how my flowmap generator works (and also real rivers), but it also causes the water to look weird sometime, even if it’s “theoretically” correct. I’m doing lots of averaging to try and make it look better. Gonna put the shader out sometime after the big forester update is finished (this weekend most likely), then do some more work on the flowmap generator and add it too.

2 Likes

Would it be possible to have many of those “quads” with water? Lets say a river going into waterfall and then continuing below. Also, water flowing at an angle (e.g. downwards).



Rivers in most games do not have proper reflections (or really blurry ones) because it isn’t possible to generate a reflection cubemap for every point on the river, but for oceans its easy because its a single plane that goes into infinity so you can simply use the camera for that.

In your case I guess that means the river is actually just a material (rather than a filter/processor) since the cubemap is static, all you have to do is configure the flow data and the static reflection cubemap.

1 Like

Nice! :slight_smile:



@androlo is it possible to add foam?

@momoko_fan

Would it be possible to have many of those “quads” with water? Lets say a river going into waterfall and then continuing below. Also, water flowing at an angle (e.g. downwards).


I think you can have many. I haven't tried, but it works just like SimpleWaterProcessor. You can restrict the size of the quad so it should be possible to set up two different processors, but it would be slow.

I haven't tried using it with anything other then a quad with normal (0,1,0), so I don't know about angles.

Rivers in most games do not have proper reflections (or really blurry ones) because it isn’t possible to generate a reflection cubemap for every point on the river, but for oceans its easy because its a single plane that goes into infinity so you can simply use the camera for that.
In your case I guess that means the river is actually just a material (rather than a filter/processor) since the cubemap is static, all you have to do is configure the flow data and the static reflection cubemap.


The system doesn't use a static cube-map currently, it uses the same reflection/refraction-system as SimpleWaterProcessor. I haven't changed any of that.

But yes, this system is more of a shading method then anything else. It can't really do anything that simplewater can't do. It just render the water surface differently.

I’ve played around some with foam, but I’m not sure what parameters to use. I considered blending in a foam texture depending on the running speed of the water. There’s normalized speed values in the flowmap generator (0 to 1), perhaps it could work.

Gonna use a directed graph instead of terrain heights.

Been devloping the directed graph concept a bit more. I think I have outlined this project pretty well, and I’m soon ready to start working.



River placement



This is how a river graph is formed.



First - all graph “activity” takes place in xz space. There’s no depth involved. Also there will be no forks in the first version (and therefore no edges). Those will be added once the system is in order.


  1. A set of verts are provided. The verts contain xz location, width, depth and an ordinal. Width & depth are scales, not absolute values.


  2. The verts are connected to form a curve (in the xz-plane). The curve is smoothed out, and then distorted by noise (one or more octaves depending on the scale), to make it look more natural. The orientation is in the direction of increasing ordinals (starting from 0 of course). The curves used by the cinematics system would be an ideal basis for this, I think.



    http://www.jamtlandoutdoors.se/images/dist/Graph_paths.png



    Note the noise is applied in the x direction in the image (just a quick sketch), but it will of course be applied in the curves normal direction.



    River generation



    The curve is used to generate the river. The generation is done in several parts.


  3. The terrain must be reshaped to create a proper riverbed.



    This is done by using a brush, sort of like how the terrain editor works. The brush I use now is a square area with size.x = size.z = 41, centered at (0,0). The active part (imprint) is the circle of that diameter, centered around the same point.



    The shape is a simple paraboloid of revolution, with global minimum at (0,0) (the value there is -10). The zeroes are in a circle of diameter 41, so it’s fairly “shallow” ( a height/width ratio of .25). All values larger then 0 is clamped so the brush never raises terrain. The equation is:



    f(x,y) = 0.025f*(xx + zz) - 10f

    height = min(f(x,z),0)



    Btw the square functions as a bounding area when selecting which parts of the heightmap to work on (it’s easy to iterate over a rectangular area). Any points inside the square but outside the circle has a function value >= 0, so they are not affected.



    There will be some noise here as well. Also several shapes could be combined later to make a deep part with shallow sides (like a real river).



    http://www.jamtlandoutdoors.se/images/dist/Brush.png



    The brush is displaced using the curve (starting from point 0), and applied every set distance. Every time it’s applied it re-shapes the terrain heightmap. It should be a pretty short distance between applications.



    This approach is good because it allows for rivers that run in any direction, and since the brush is xz-symmetric it does not have to be rotated, just applied.



    The parameter used for the noise function is the total displacement of the brush along the curve, so that a tiled 2D noise can be used to create some variation in width, depth and also the fine detail.


  4. The river data must be generated.



    This I will do in several steps as well. First, a new array of data is generated. It records data at the points where the brush was applied. Each such point contains the xz-location, the depth and width used (for volume flow calculations), and the tangent of the curve. Normal values are calculated later by (z,-x)'ing the tangent.



    A float array is also used, to store every point touched by the brush. It is needed to find out which points are part of the river, and which are not. The array stores relative heights; any 0 value is considered to be outside the river. It is also used to calculate depth at each of the flowmaps sample points, so that water run slower along the edges etc (where depth is low).



    This array is needed for splatting a river texture onto the terrain alpha map (among other things). I think I’m gonna do this radially from each point, using a constant value with a steep falloff near the edges (and maybe some noise to avoid banding, depends on how it looks).


  5. A river mesh must be created. This is why I want this approach. The height of the terrain does not matter since the brush just displaces the height values, it doesn’t set them.



    Below is a representation of the above mentioned points. It is very coarse (normally there’d be much more lines). Either way, the mesh verts are created by extending the points in the curves normal direction. This way xz coordinates for the verts are created. Btw. this mesh will of course be a triangle strip.



    http://www.jamtlandoutdoors.se/images/dist/River_points.png



    To get the height coordinates just sample the heightmap at both xz-values, and use the lowest value. The mesh will follow the terrain pretty well with that approach. Needless to say, there might be some post processing of these values required (perhaps some smoothing).



    The issue of heights increasing will be adressed as well, like terminating the process if height increases are to weird, or maybe just dig deeper.



    The width of the mesh should definitely be extended to make sure the water mesh is always under the terrain. This can be done with a global widening constant (like 1.1 or 1.2). Also, a waterlevel should be defined. It should be negative, and the entire mesh should be displaced by this value (so that the water mesh isn’t the same height as the terrain, but slightly lower). It should be possible to change that value.



    On top of those things, the varying elevation of the river should be stored alongside the xz values of the river curve. These values (datums) could be baked into a texture (perhaps the flowmap) to make use of in the shader.



    Shading



    The flow part works in the xz plane, and doesn’t care about heights. This means the flow works just as well on a flat plane as it does with varying heights. If the height varies a lot over a units length it may be necessary to scale (since the segment will be longer then if it were flat), but it’s not a problem.



    Heights, and especially height deltas, could be used when calculating wave speed and turbulence. This is simple in theory, but it may be hard to implement in practice. It’s hard to make it look good.



    The real problem is reflection and refraction. Gonna have to read how people do this in other games. Skyrim for example has rivers and streams that look pretty complex (not just flat).



    With refraction, I think the main difficulty will be to set up the camera properly.



    When the camera is set up properly, I think it’s just a matter of using both the mesh normal and wave normal. Currently it uses the wave normal (a very common approach), and it works well.



    I think I will use the sky only for reflection. At least initially. That removes some problems.



    Again, these are just some thoughts. Gonna read about it more before implementing anything. Also there will probably be a flat mesh alternative (that’s how it works now).



    Implementing in jME



    Gonna go with the simplewater approach. It has cameras set up, and seems to work pretty well. Again, I will drop the reflection cam at first and use a skybox instead.



    A lot of this stuff is already prepared. It will take time tho. I figure by this fall maybe there will be a working version (considering I got lots of forester work to do, and the sky stuff, and also other work outside of programming).



    EDIT: Hmm need to specify, the terrain heightmap isn’t really re-shaped, like i write. It is a typo. A new heightmap is of course created, since the modifications happens one at a time. The original heightmap is left intact, and only a copy is modified. Each time the brush is applied, an array of the same size as the brush is filled with the original heightmaps values minus the depth. This array is then written into the new heightmap.



    Also, the map with the relative depth of the water is then calculated by subtracting the new one from the old and normalizing.
7 Likes

Cool stuff. :slight_smile: