Wavesets explained

Help bring our custom Ages to life! Share tips and tricks, as well as code samples with other developers.

Wavesets explained

Postby Sirius » Sun May 24, 2015 7:16 am

So recently Tweek has been asking about in-depth explanations for Wavesets. Since I was quite interested myself, I decided to try tweaking various settings and note down my observations.
Well it's taken some time to understand what each setting did, but now I'm able to quickly setup any kind of water system and get satisfying results, no matter what kind of water I'm looking for. I still only know the use of half the parameters, but these look like the most important anyway. (oh, and I didn't experiment with shores either)
For instance, I've setup the two following wavesets:
One with calm water but lots of wind
One with big waves (a bit glitchy sometimes, but seriously ! did you even know Plasma could handle that ? :o )

And here is the AlcScript for these !
Windy water Show Spoiler

Wavy water Show Spoiler

EDIT: more recently I setup another ocean with Noloben's sky, if you want to have a look (it's mostly towards the end of the video)

Here comes the notes. Over the past few days I rewrote these to clarify as much as I could the use of each setting, which might explain why they are so long :P
Hope these can be useful. Happy reading !


UPDATE ! You can now test most of these settings in real-time in my Age, Denkasen ! Much easier than understanding all that long writing, believe me.


Obviously, a good place to start and get an example AlcScript is the Wiki article
More infos about the math in use by the engine is in the GPUGems book. Keep it in a tab somewhere, even if you don't understand all, there are some interesting screenshots.

Quick reminder about the water mesh

The object's origin should be located at the water's height. The faces, however, are recommended to match the bottom of the pond/lake/shore/whatever (see figure 1.7 of the GPUGems article to see how it should look like). This is not required, but allows, for instance, calmer and more transparent water where it's less deep. If you prefer, you can just drag all vertices a few feet below the origin. However, do NOT put the vertices at the origin's height - this would result in invisible water. Similarly, avoid vertices higher than the origin, they result in weird non-flat water.
About the faces themselves: it's good practice to put a lot of small faces (1 to 10 feet wide) near the avatar's path, which will avoid weird behavior with reflections. Farther away from the avatar (near the horizon, for instance), faces can be a LOT bigger. Try to avoid long and narrow faces, though.
Also, if you intend to use geometric waves (waves that actually move the mesh, detailed below), you might need a high number of small faces as well. Make sure you get enough for it to look good, but not too much so as not to slow the game down.
For instance, here is a "good water mesh", which is used for the wavy ocean in the video above:
Show Spoiler

Also: I wouldn't recommend lighting wavesets - since they are quite complex, I'm not sure lighting would work for them.

The most important: GeoState and TexState

Essentially, what controls 80% of the appearance of Wavesets is TexState.
TexState's parameters are the same as GeoState's. While TexState controls the dynamic texture of the water, GeoState controls deformation of the water mesh. GeoState is rarely used at all by Cyan, because it is calculated per-vertex, and requires a very high poly mesh to look fine. TexState work per-pixel, thus working at smaller scale, to control ripples on a calm pond.

If the description is not enough, you can fiddle with the parameters in a test scene. However, since TexState is flat, variations might be subtler to understand. GeoStates, though, make differences very easy to spot since they result in an animated mesh.
The following description of parameters might be easier to understand if you think of them as mesh deformation. Once you fully understand what they do, you can just apply these to TexState, albeit at a smaller scale. This usually works fine for me.

How to use GeoState and TexState

You probably know in math, the most simple and yet more "wave-looking" function is the sinus function. Wavesets use sinus-like functions to create waves, so if you're familiar with words like amplitude or wavelength, you probably won't have difficulties understanding what will follow (if not, play the monitor calibration at the beginning of Myst IV !).

MaxLen: max wavelength (~width and spacing between big waves, or wave outline if you prefer). Higher values might also increase waves height.
MinLen: min wavelength (~width and spacing between smaller waves). Obviously, this must be below MaxLen.
AmpOverLen: global multiplier for the water's height (as its name suggests, this is amplitude/wave length). Closer to 0 means smaller waves, bigger values result in high waves.
Choppiness: controls the crest sharpness (see figure 1.5 of the GPUGems article). Close to 0 means round rolling waves, values around 3 mean very sharp crests.
Angle dev / spread degree (radians on PyPRP, degrees in Max): controls whether the waves all go in a single direction, or if they should be more noisy. Waves are created by the meeting of two sine function, going in slightly different directions. See figure 1.4, left image of the GPUGems article.
If you have very uniform wind and a wide, flat water surface, use values close (but not equal) to 0rad / 0deg. This also works for beaches, as waves climb the shore in a specific direction - it would look weird if they were sideways.
If the wind is very strong and changes direction often (storm), or if the water body is surrounded by cliffs, use values around 1.4rad / 80deg. This results in more agitated, less predictable water.

Usually, you should set a long wavelength for geostates (because they control the "big waves", which need all the vertices they can get), and a short wavelength for texstates (which aren't limited by the precision of the vertices, and thus control the smaller waves on the water's surface).
Cyan never used geostates (they set their amplitude to almost zero), because they rarely needed big waves, or didn't have enough vertices/performance to spare on these. They instead rely on texstates, which give the calm feeling of water in most Ages. GeoStates allowed them to have very discreet, high-span turbulence over their water ponds.
Use geostates only if the resolution of your water body is high enough (1 vert every 3 or 4 foot. Depends of your waves). NEVER disable geostates altogether, it causes issues on a lot of PCs.
Because mesh modification is expensive, you may encounter high lag in your Age if you set GeoStates wrong (yes, even with a recent gaming PC which can run state-of-the-art videogames). I don't know exactly what causes this lag, I think it's related to having too small wavelength in the geostates.
However, you can manage to get a huge ocean with big scary waves without much drop in performances, so keep trying.


Besides TexState and GeoState, there is something else you might want to look into: specularity (it's not really specularity. Just think of it as more options to control the TexState). PyPRP's defaults are usually good enough, but it's always better if you can fine-tune it yourself.
specnoise / Noise %: multiplier for the noise texture applied to the water's surface. 0: no noise 1: high noise distortion. This is useful to fake raindrops on the water - for instance, Kemo increases this value during thunderstorm, because of the rain falling in the pond.
specstart and specend / Ripple Falloff Start and End: these control the visibility distance of the TexState (and the noise texture previously mentioned). Below the start value, the TexState is fully visible. Above the end value, the TexState is fully faded, resulting in completely flat water - the GeoState is left untouched, though. Plasma fades the TexState between the two values.
This is useful to avoid aliasing of far-off water, since small ripples seen from a distance only result in "noisy pixels". It also gives an (insignificant) performance boost to performance.
For an ocean, recommended values are: End = how many units to the horizon, Start = 1/4 * End. This should result in a smooth transition, which keeps enough details and avoids aliasing.
Note: this is computed per-vertex, you will not see any difference if your water mesh is a simple flat plane.


Another value which you might want to use, is RippleScale. This controls the overall scale of the TexState. This might be useful if you want to scale up or down the noise texture. Obviously, you'll have to scale the MaxLen and MinLen of the TexState accordingly, since they are affected by RippleScale.
If you don't have any use for it though, you can leave it to 100.


Most other values can be left as-is, which is what Cyan did most of the time. They are rarely modified, and shouldn't result in very high variations. TexState and GeoState are what you're looking for, mostly.

Vertex Colors

Something worth noting: Wavesets don't use the VertexColor layer as most meshes do. Here, it doesn't control the mesh's color (useful to fake lighting), but it allows you to tweak some water params on a per-vertex basis.
Red = opacity of the water. A cyan color will make the vertex almost invisible.
Green = described as "greyscale color" by the Max plugin. The article on GPUGems says it's the "strength of the reflection on the surface, making the water surface matte when green is zero" (green at zero = magenta color). Still not sure what that means, though.
Blue = fresnel strength. Basically, Fresnel is the water's reflectivity, in relation to viewing angle. Meaning a yellow color makes the vertex more transparent when viewed from a shallow angle (or the other way around, I'm not sure).

Controlling the reflections

This is not really a waveset option, but is important to get the water to reflect what you want.
refreshrate: controls how often the reflection is recalculated to match the real scene. I suggest leaving it at 0 for performance reasons. If you have a slow updating sky, you could put it at 0.5. If you're slightly crazy, and want water to update in real-time, put it at 0.01. But now you're asking for trouble, and better have a good PC.
hither: minimal distance for object to be included in reflections. In my example Ages, I set this to 3000, which makes sure the buildings nearby do not get rendered. Why ? Because CubeMap sucks, and reflections always end up wrong for close objects.
yon: maximal distance for objects to be included in reflections. I'd recommend setting this quite high, which will make your sky visible in reflections. Because it's far, the sky doesn't look wrong when seen through the cubemap.

Hither and Yon are distances from the water's object center. However, they don't result in a spherical shape below (or beyond) which things will get to be rendered.
Actually, you need to think of these as a cubic shape (hence the name, "cubemap"), with the distances being half the cube's size.
Generally, if your water renders as white, then Yon is too small and your skydome is not included. On the other hand, if there is a weird reflection which is not the sky seen in your water, you probably want to raise Hither.

Lighting of Wavesets

As I said, I doubt lighting the waveset mesh directly is a good idea. However, there is one cheap trick that can modify water brightness: subtractive textures.
Basically, some textures in Plasma can add or subtract their intensity from the object behind them. This is useful to change light intensity for objects, without masking them entirely. For instance, the sun in Noloben adds its own color over the sky, resulting in a VERY bright sun. And yet, you can still see the clouds behind it, because the sun's texture is not masking them.
However, you can also use textures to remove brightness from object located behind them. In the case of water, you could put a plane just above the water mesh (assuming you don't overdo GeoStates), put an almost-white texture on it, and set it's Blending mode to "Subtract". You'll also need to set its passindex to above the one of the Waveset. This will result in the water being darkened where it is seen through the second plane, as if it were receiving less light.
You could also replace the whitish texture with a lightmap (remember to make it NEGATIVE first !)

I didn't test it personally though, so I'm not sure it will work without any further tweaking. Food for thought, though.

Cyan used a similar technique in Laki'Ahn, to expand the feeling of the water's depth: have a look in the Age, near the shore the water is a wonderful green color. And yet, further away, where the water is deeper, it gradually fades to a darker blue color, as if no light can travel through to the sand and back to your eyes. Neat, huh ?

Python stuff

It's worth mentioning the Python API allows animating almost any of these parameters smoothly. As previously mentioned, this was used in Kemo to change the water's appearance during the rain, but it might be useful to you too - I'll leave you to thinking what you might achieve with it...
Last edited by Sirius on Wed Aug 05, 2015 2:19 pm, edited 1 time in total.
User avatar
Posts: 1455
Joined: Mon Jul 26, 2010 4:46 am
Location: France

Re: Wavesets explained

Postby Tweek » Sun May 24, 2015 7:54 am

Doing Gods work son.
Beneath - IC Blog.
Beneath: Ages of Tweek - FB Age Dev Page.
User avatar
Posts: 692
Joined: Sat Sep 29, 2007 6:37 am

Re: Wavesets explained

Postby dendwaler » Sun May 24, 2015 8:17 am

Thx Sirius for your excellent explanation!
Incredible that you have gained such a good understanding of this difficult matery in such a short time. :geek:
Those wonderfull Worlds are called " Ages" , because that is what it takes to build one.

Watch my latest Video Or even better..... watch the Cathedral's Complete Walkthrough made by Suleika!
User avatar
Posts: 935
Joined: Mon Jun 22, 2009 10:49 am
Location: Nederland

Re: Wavesets explained

Postby janaba » Sun May 24, 2015 8:28 am

Tweek wrote:Doing Gods work son.

Hihi, sweet, indeed, awesome job, Sirius ... :)
User avatar
Posts: 918
Joined: Sun Feb 05, 2012 3:27 pm
Location: Berlin, Germany

Re: Wavesets explained

Postby Karkadann » Sun May 24, 2015 10:22 am

a good example of what can go wrong with reflections can be seen in the chess age, not sure exactly what happen but the reflections of
what I think are the lily pads are way out of scale, interesting as it is if your shooting for accuracy make sure you double check your work
I think the reflection is the original scale of the lily pads, somehow it must have retained a memory similar to if you rescale a kickable
with out going into poly edit first
The Optimist see's the glass half full, The Pessimist see's the glass half empty.
Its the Realist who see's the glass is half full with air, half full with water
User avatar
Posts: 1223
Joined: Sun Aug 02, 2009 10:04 am
Location: Earth

Re: Wavesets explained

Postby Sirius » Sun May 24, 2015 12:59 pm

Wow, thank you all for your kind words ! :D

Karkadann wrote:a good example of what can go wrong with reflections can be seen in the chess age, not sure exactly what happen but the reflections of
what I think are the lily pads are way out of scale, interesting as it is if your shooting for accuracy make sure you double check your work[/url]
I think the reflection is the original scale of the lily pads, somehow it must have retained a memory similar to if you rescale a kickable
with out going into poly edit first
No, actually, that's no the same issue as with kickables. Such deformations always happens with water reflections, because the reflected scene is always rendered as if viewed from the water's origin (which is why your lily pads look so big: because they would look big if viewed by a frog just below them. Because that's where the water's origin is).
Cyan did a pretty good work making sure this doesn't show a lot in their Ages, however when you look carefully it still happens in some places. (That's also why reflections never show smaller object like trees, and such: because they always look misplaced)
To avoid that, a solution is to move the object's center away from any such object. The best solution is to use VisRegions to exclude nearby objects (in Max, that's the "Effect Vis Set" in the "Dynamic Environment Map", according to the documentation), although they are a bit tricky to setup.
As a very simple workaround for PyPRP, in "Controlling reflections" I recommend setting the "hither" value for the cubemap to something a lot higher, as it will simply ignore close object. Unfortunately this parameter is not available on Max...

dendwaler wrote:Incredible that you have gained such a good understanding of this difficult matery in such a short time. :geek:
Thanks ! Fortunately, there were some infos on the Wiki already, and some in older topics. As for the rest, I either tried to cross-reference it with the GPUGems article, or replaced the defaults with crazy values and see how it rendered in-game (modifications to GeoStates are pretty easy to spot when dealing with huge vertical waves :P ).
User avatar
Posts: 1455
Joined: Mon Jul 26, 2010 4:46 am
Location: France

Return to Scripting

Who is online

Users browsing this forum: No registered users and 2 guests