[WIP] River shader

Been making a river shader these couple of days, thought might as well post it here.

Goals

  1. Flowing water
  2. Accelerate on slopes
  3. Foam on slopes and waterfalls
  4. Foam on collision
  5. Follow mesh geometry

Didn’t go for the “generate albedo from dark and light colors” method, just used some textures I got here: http://kay-vriend.blogspot.am/2013/10/wavy-water.html

Current solution:

Flowing water +
Accelerates on slopes (blend two material functions with different panners)+
Foam on slopes and waterfalls (additional textures blended on top)+
Foam on collision (via depthfade)+
Follow geometry (just unwrap the geometry as a rectangle and set the width to the UV cage)+

7eae1f343cb97fa0576d50d1fe1d1272a0247b47.jpeg

4c80a6e49dc39b99274c30bdf8f6e1603f8ab7f8.jpeg

Drawbacks: 16/16 textures, resource-heavy, needs fine-tuning to look nicer (it looks a lot nicer in action, than on screenshots, as motion blur destroys the effect). No blended foam under waterfalls, would like nice radial foam like that in Skyrim.

Video and the shader itself incoming.

Masking the slopes

3cd476f8af1822ab693087a79fca15c107315d67.jpeg

What it actually does: compares the vertex normal in World Space to the z-vector with a Dot function. Two params to control the slope. Outputs slope as a gradient. Why the clamp after the lerp? Was sleepy, replaced the clamp with a lerp, forgot to remove the clamp. One minus function to reverse the gradient.

Speeding up on slopes

What it actually does: made my water into a material function - mf_water, made two panners with different speeds, hooked to the inputs. Blended using the output of Slope Blend. The third panner is for the foam.

Blending in the foam

What it actually does: takes the mask, the standard macro-variation texture, adds in the oneminus result of DepthFade and masks a highly tiled (100x) foam texture. Lerps it with the base color, adds to the opacity and roughness too.

Refraction and the rest

ade0fbc6594df9975ccb893e60cac2edf7c87ce3.jpeg

Nothing too fancy about the rest of it, mostly the standard stuff.

While I’m busy with the video demo of the shader, I’d welcome any thought or advice on this project. If you decide to use it in your own project, it would be nice if you posted a screenshot of your result here.

This looks wonderful - my question is what you’d sacrifice if you wanted to optimize it down (say, for VR). It might be worthwhile to have a low-performance version you could switch to.

Obviously, tesselation. Wouldn’t need a height texture anymore then. Could also stick the foam, gloss and macro into one texture as layers - they don’t contain color information anyways.

By performance-hungry, I meant it was running on ~45fps on GTX660M. Adding further detail to the level might bring down the fps to 30. Shouldn’t be a problem for a more high-end GPU on VR. Problem is - doesn’t look good enough to require a high-end GPU for 60fps.

Actually this is a starting point only. I’m going to improve this until I get a completely spline-based and sufficiently realistic result. One of my reference points was the CryEngine river tool -

http://docs.cryengine.com/display/SDKDOC2/The+River+Tool

Also would like to implement some kind of fluid surface technology in it too. Might eventually be forced to write a plugin - and I’m very lazy when it gets down to coding stuff.

The results will be documented here. Hope it helps someone.

If you followed on my Godrays tutorial, you might have noticed I tend to slack when it comes to polishing work :smiley:

Thanks for the praise btw :)))

Awesome! I’d advise you to use DepthFade twice: one with a distance of 40 to lerp between 0 and half opacity, and the other with distance of 200 that lerps between half and max water opacity. This way the water will look dirty, but vertex blending between the landscape and the water will look smooth :slight_smile:

As I decided to rewrite the shader from scratch and this time do it properly, let’s start with some reference images on what we’re trying to achieve.

Observations

We have river rapids in this picture. Because the flow is strong, water isn’t really translucent here. Sharp contrast in roughness between foam and water surface. Round flow ends in foam, sharp waves with foamy peaks.

Simplified breakdown of dominating colors in HSB:

206/92/25 - deep calmer water: Deep marine blue, B going to G
189/43/64 - waves and more shallow parts: Bright azure, B going even further to G, almost cyan
240/4/99 - foam: Very bright, very desaturated blue, almost white. Due to low saturation, hue is irrelevant. Can be replaced with .99 white.
215/18/76 - foam self-shadowing: Bright, desaturated, grayish blue. Due to low saturation, hue is irrelevant. Can be replaced with .76 gray.

Fresnel controls specularity/opacity balance.

Increasing the contrast on the heightmap might be the way to go with foam. Would give a nice and even foam map on sharp edges.

Thanks. Was thinking of that:)) to make the water exactly on edge to be translucent. Will try tomorrow. Right now I’m just breaking down how real river water looks for a more realistic result. Also, I decided to experiment with flow maps to make the water flow more uneven. Too bad I can’t generate flow maps on the run based on DepthFade value to distort the texture - that would be just awesome.

Meanwhile, I tried what vinz243 suggested, here’s the resulting mask:

Might be even more useful than it seems; could blend in a different flow with it to make the waterfall more organic.

A really interesting read - Valve’s take on water flow in Portal 2

http://www.valvesoftware.com/publications/2010/siggraph2010_vlachos_waterflow.pdf

http://www.valvesoftware.com/publications/2011/gdc_2011_grimes_nonstandard_textures.pdf

Development report, 24 Sep. 2015
Part 1

(I’ll be posting reports of my progress and findings once in a while)

A couple of things I learned these days.

Mathematically computing waves inside the shader is not the way to go. UE-s means of pattern generation are not that convenient to procedurally calculate a Gerstner ocean via math. A Tessendorf model would be even harder to implement.

The idea I had was to model the large waves via math, and then use height and normal maps for the small detail. While easy on the texture side, this method would blow up my material complexity to the point it was no longer worth it.

On the positive side, sine waves can be blended together to simulate more “calm lake” kind of displacement. The pattern can be scaled and re-blended at will, might be a good procedural way to get rid of tiling artifacts, if blended with a height map. More on that once I experiment with that. Also - Aurora Borealis can be easily represented with this method, using sine waves of different lengths and blending colors through them. The idea is to have several sine waves (panned in the same direction) acting as masks for lerps with non-zero lower value. The exact top and bottom values are to be determined via trial and error. Then the lerps can be multiplied together for one big pattern - or be used as masks to blend colors you want to have in your Aurora Borealis. Hope will have a working example soon.

42f249f508f518a1b1beae34816005c292b5fb48.jpeg
(early experimental example of sine-based pattern, an overlay of 2 horizontal and 2 vertical sine waves)

The theory behind getting a sine wave out of a sine function and local coordinates is the following:


What it does: takes the local coordinate values from either U or V directions and feeds them to the sine node. The resulting sine function varies between -1 and +1, but we actually lose the negative values as they cannot be represented as color information. So we add 1 to make it vary between 0 and 2, and divide by 2 to bring it to the 0-1 interval we can use.

This approach is easy to parametrize:

4940b9493746b33f8904f381e748cba908ad7664.jpeg
This wave can be placed inside a material function to be called upon with necessary inputs for a procedural sine-based pattern creation. The only problem would be UE panners not accepting inputs. So you might have to place a vector2 input instead of the TextureCoordinate node and feed the panner directly into the function as an input, but remember - the scaling and offsetting will affect the panning too.

Development report, 24 Sep. 2015
Part 2

For spline-based water generation, tessellation is not the way to go. Problems are numerous - heavy on the hardware, tearing at the edges of patches, poor optimization options.

The best answer I found was the one offered in “Water Technology of Uncharted”](http://www.gdcvault.com/play/1015309/Water-Technology-of) by Carlos Gonzalez-Ochoa, Naughty Dog (GDC 2012). It substituted tessellation with LODs. The smart thing about their method is it gives you greater control over how you want to optimize your scene. A good hint was about building the LODs: if you just add divisions to the plain, you’re still going to have tearing on the edges as the lower LOD levels are going to have more vertices on edges, thus deforming differently. The smart fix?


image (c) Naughty Dog

There’s a bit more about it, than having the same topology between LOD transitions, so I’d suggest you take a look at the whole chapter - it’s an excellent read, and might get me to drop tessellation altogether, and not just on water surfaces.

How is it going with this and do you have a video?