Unreal Engine Tutorial: Water Simulation (Gerstner, Circular Waves)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello i thought i would make a new video to replace a couple of old videos i have on how to make water waves so this is based on the nvidia effect of water simulation page um which i reference here i'll make this slide deck i'll put it in the details so anybody can get to it if they want but if you have been here to this webpage they have a lot of functions and variables on how to create your own waves and a lot of it is a little hard to understand and read some of the some of the way the functions are written out is confusing which can make this hard to use so it took me some time to get through all of it and to get what i want out of it so i wanted to make a video to show how to do it so to start with we're going to need a mesh and we're also going to need a project in unreal engine i just started a new one that's empty from the third person map and on religion 5. so just wanted to say that so if we go into blender we're going to need a plane that we know the size of and we're going to need to tessellate it so that we can you know see vertices moving when we make waves so let's do that blender real quick so shift a add a plane let's take a look at the size of it let's set it to 128 by 128 centimeters and apply alt transforms go to edit mode here and we'll hit ctrl e and we'll subdivide so what i'm trying to do here i want to test later down to one centimeter edges it's down to two it's down to 1. just to make it easy right so we know that this thing is 128 across and we know that each face is 1 centimeter so let's go ahead and export that i have a preset for static meshes that just exports the selected object and on the geo it sets the smoothing to face this way we don't get any warnings when we import it so we'll start with that there it is tessellated plane and i'm going to turn off generate missing collection and i'm going to turn off generate light map uvs i kind of like to do that stuff myself so now that we have our plane and if we look at it in wireframe we'll see that it's very teslated and this is you know so that we can see what our shaders are doing right okay so we have our tessellated plane let's make a new material um and let's set it to unlit which makes it easiest to visualize first okay so let's look back at our water simulation here all right so the first thing that we should do is just visualize something simple with a sine wave an amplitude and a wavelength right so let's go back to here and put that on the other screen so let's just go ahead and make if you hold down one and left-click you'll get a constant so let's do 10. times let's select our tessellated plane and use that here all right so 10 times the sine if you type position here you'll get world position which is a bit weird i know then times sine of two well two actually wait let's do two divided by yeah so we want the wavelength so what i'm what i'm heading towards demonstrating here is that um we want to be sure that when we choose a wavelength that it's accurately represented through the sign that the size of it as is real right that this wavelength is 128 centimeters okay all right so 2 divided by wavelength times x oh we're going to need x just x here so let's set that to just r channel which is x yep that goes into the sign and now we can go into miss of color okay so this is 10 times the sine of 2x over 128 right so if you notice here the period of the sine wave is wrong right there's there's on and off there's multiple here when our wavelengths 128 it should fit the whole plane because the whole plane is 128 across right and the thing to realize here is that the sine waves period by default is one which is weird like think about that for a minute normally sine waves you would want to pass in radians which would mean 2 pi would create the whole period right so let's put in 2 pi here so 2 times 3.1415 is close enough so that's 2 pi now you can see this is starting to look a little better but radial frequency which is 2 pi 2 pi f or 2 pi over length is the real way people do it is the real way electrical engineers use radio frequency i don't know why um nvidia has 2 over l without using pi so this now this will fix everything right there we go so 2 pi over wavelength times x and our sine times this amplitude will give us this right so let's make a float three so that we can see the world position offset working as well there our preview scene is cutting off everything so let's turn off the floor there we go okay so you can see that that sine wave is producing this world position offset and that 128 wavelength is filling up the whole plane which is correct if we do 64 here we should see two right yeah see so our sizing is correct now that our sign has a period of two pi okay i wanted to show that you could do all of this with a custom node too for those of you who are interested in that so custom node is a way to enter just hlsl code and do the exact same thing that these nodes are doing although these nodes allow optimization epic has a way of code folding that like reduces the instructions if you don't use a custom node so you shouldn't always use a custom node unless you know there's some real reason or you know you just prefer it but i would be aware of possible performance issues so let's recall this so if we we can just return a float 3 and we want nothing in x nothing in y and then we want what 10 times the sine of 2 times pi times position yeah right is that going to work no because our input we don't have an input for pi although i guess i can say float pi equals 3.1415 right now the only thing we're missing is position so we're going to need an input going to need an input for position right should be this okay i messed something up oh forgot to do a close brace there all right so now we have done that the exact same thing within reason here um we don't we didn't use the input for wavelength um and our output for the world position offset you know is only the z so the miss of color is blue instead of white but you can see you can you can do the exact same thing here with with that kind of code all right so we've done that oh i wanted to mention something which is the nyquist frequency so if you think about this wave and how or this plane and how it's been tessellated right there is a limit at what wavelength can be represented on these vertices right so let me hook this back up so that we can see this better i'll just kill this for now and so what would that be right if you remember we cut it down to one centimeter so can it represent a one centimeter wavelength the answer would be no right the scientist named nyquist figured out that it had to be a little more than two times so you'll see two is not right but 3 we can start representing see so maybe even 2.1 let's see what 2.1 yeah it looks pretty aliased looks pretty messed up right this is because the wavelength is just too small or the frequency is too high to be represented by that right so be careful when you start to do this to realize that because it really low load frequencies like our wavelengths we could get really bizarre results see look at that and of course if you try to go like 128 can fit on this right but if you do 256 you're going to get half of a wavelength so you start not being able to see um right that's only a very small part of it now you can't actually see the waves so keep that in mind that the way you tessellated it and the size of your your plane is going to affect what waves can be represented on it and i talk about that here okay let's move on to the general sine equation that's on the nvidia site so you can see here this equation is amplitude times the sine of the dot product of the direction and the position in xy space times this wi radial frequency plus time times this qi which is a phase constant and i wrote them out here a little more clearly amplitude times sine the direction and of course wavelength is 1 over frequency radial frequency is 2 pi f or 2 pi over l the phase constant is speed time the radio frequency and i wrote down some of the units there so let's go ahead and build that material this color let's set it to unlead again so the first thing we're going to need is the direction so i'm going to use a parameter for this but this direction is only in x and y so you're going to want to append an x y vector like that to make one right [Music] so we've got the direction yeah you might want to follow along with the equations on the side on another screen or something because i don't have an easy way of showing it at the same time but direction dot with position and xy space if you remember so let's i always like to exclude the offsets unless there's a reason i want them okay xy position see let's make the direction be just in the x direction right now so now we're going to multiply that by the phase constant right so the phase constant is 2 pi over wavelength so hold on click to pi if you hold down s and left click you will get a parameter that's scalar so let's go ahead and keep that at 128 so we can make sure that it's the right size okay so this is now a radio frequency so i'm going to mark it right this is our direction this is our position right okay so if we multiply the dot product that times the radio frequency i'm going to add next we're going to add time so we go add time times this the phase constant so the phase constant is speed just leave it at zero right now speed times the radial frequency so that's our phase constant which i will just call q for now even though it's a greek symbol they're not easy to do in here or maybe even possible i've not not tried i wouldn't like put unicode characters in here that would possibly cause issues okay so that is all the stuff that we're going to feed into the sign if you remember once again the sine wave needs 2 pi as its period if you don't want things to be messed up let's multiply this by a amplitude let's take a look at what it looks like there we go that's exactly what we expect one full period of the sine wave in the x direction and if we want the world offset to be in the z direction only which we do we can do this so the amplitude is pretty small let's do 10. you can see that is working right all right so this is looking good you can also do this in a custom node which i have in the slides if you want to see it but i'm not going to do it right now so what if we make this lit okay let's unhook this let's make a base color that's just gray so point one eight let's make a oh oh no let's make it metallic we're gonna make it really reflective just so we can see stuff right all right so if we look at this now you can see the waves are bending it and we're seeing a shadow but the normals are totally not updating right so we need to also calculate the normals so let's do that next so one thing you can do if you don't know how to calculate your normals and you just want to see what the normals might look like or should look like you can do this cool trick so take position and this is after the offsets right so this is the actual position of each vertices and we're going to use ddx ddy which are the change in x and the change in y i'm going to feed those with the position and then we're going to do a cross product between the two and that creates a normal so let's let's visualize that in both ways all right so you can see our normals are now bending like they should but they're very usually very harsh you can see like these lines in here it doesn't smooth the normals it just creates exact normal so this helps us to know what this should look like you know it's a ground truth we can compare compare with if we need to so let's just leave that there but we want to calculate our nulls directly all right so let's talk about that how to do that so the nvidia site has equations for it it says the normal is the cross product of the binormal and the tangent vectors right oh one thing i forgot um when you do this kind of thing for normals uh you got to turn off under advanced here you have to turn off wherever the heck it is i'm too far down i'm trying to turn off tangent space normals here there they are see them all right tangent space normals yes because these normals are generating are in world space right they're not in tangent space so be sure to turn those off if you want these to look correct anyway to continue again on the normal equations here normal is the cross product the binormal and the tangent vectors which is kind of what you do here that's what this is um but we can calculate it directly the normal is minus the partial with respect to x of this equation h minus the partial with respect to y of this equation of the same equation and 1. so if we look at how to partially differentiate this equation you can see here it's just the differential of that original sine equation which you know differential of sine is cosine right and then you differentiate the stuff that's inside and that all pops out so how did that happen if you want to understand how you get this value and what this means i did a little example here so here's the original equation and we're doing a partial with respect to x right so we know that the differential of sine is cosine so we just do a i times cosine and we got the original thing that's in there times the partial of what was inside of it right so then we need to rewrite what's inside of it to make it make sense let's replace the di.xy part with the definition of a dot product so this is for each the each component in the uh and the vector right so 1 would be x 2 would be y so i replaced it with the definition so we have the direction in x times x direction and y times y that's that's what these are right the d i dot position and if you think about it so since x is the only variable here this gets wiped out this w i multiplies by this then this x being differentiated returns the direction x times wi this part goes away as well right so that's how you get there okay so let's build that result if you notice the thing being fed into the cosine is the same as what was fed into the sine right here's what's in the sine here's within the cosine so we can start with that so let's go ahead and just pop a cosine on here remember two times pi we're going to get the wrong period so we take the same thing that's feeding the sine we feed the cosine now we multiply by the amplitude we multiply by the radial frequency we multiply by negative one i'm going to combine some calculations here to make this cheaper all right and now this this result here we need to do the things that that make it specific for x or y so let's go ahead and make a float 3. so if you look back on this page you'll see it's 1 and z and the minus of these two right so let's pop 1 and z okay and this multiply is going to be with the x component of the direction so here's our direction right so we need to use a component mask which is shift c to pop that up and let's go ahead and do x so we multiply by x and this will create our x normal and now we can duplicate this use the y mask instead this goes here again whoops minus one oh wait no not minus one doesn't matter there's the x mask there's the y mask we've already got the minus one there i forgot to hook up that to the direction vector there okay there you go you can see the normal let's compare it to this one here it looks right to me question mark let's see let me re re-look through the equation here so cosine times amplitude times radial frequency times minus 1 times the x component of direction all looks good so let's deconnect the amiss of color here and look at those normals does that look looks about right to me so now that we've done that let's see we should convert this to a material function if we want to make some gerstner waves because they're very similar to this and usually the point of gerstner waves is to add multiple waves together right so let's make a material function so really you can just copy all of that paste it into here and that output there was the world position offset and this output here was the normal we need another output function output right okay so we need to get rid of all of these scalar parameters and vector parameters and make them function inputs so like you can hold ctrl and left click you can move it like that so there's amplitude let's see um probably easiest to hit control d and just duplicate some of these singular scalar ones let's name this one speed so speed default to zero amplitude let's default it to 10. okay and let's create a wavelength one i'll default that to 128 since it fits our plane all right really the only thing left i think is direction and it is actually a vector 2 because it's an x y direction oh one thing i forgot here we've got to normalize this in case somebody puts in something nasty so we don't need to append it anymore so we need to normalize this otherwise you will get weird scaled results like if you make direction two instead of one on the x it will it will scale things by two okay so this is still direction right d um oh look we lost where the direction was coming out there so let's fix that actually i should call it direction after the normalization instead of here to make sure that that's where i use it from okay now that we've done that let's put that material function back in here and move the normal right there's that here's our amplitude which we can just hook directly up here there's our speed here's our wavelength here's our direction all right there we go so our normals are good and we've got a singular material function that does all of this okay so that all looks good next thing we need to do is turn these into gerstner waves so the gerstner wave z part is the same as what we've already done but adds an x and a y part and the normals also change so let's go ahead and build that new material function if we want cursor waves so if you duplicate this one let's get rid of this okay so this z part is already fine right but we need to add some more cosines for the x and y part and i just copied it so that i would keep the period correct so the cosine is actually fed with the same thing the sine is fed with as usual it's multiplied by q i a i so what like an upper case q the uppercase qi is the wave constant so let me do that real quick so well first we're just going to multiply by amplitude because that's the ai part all right let's see whoop so let's look up the qi part real quick here it is so q i is one over w i a i number of waves okay so one 1 divided by radio frequency w i times amplitude times the number of waves oh whoops that needs to be a function input right scalar okay so this is the uppercase qi okay so it's the cosine being fed with that stuff times a i times qi times the direction x for the x1 so let's mask that for the x let's get the direction as usual so that's the x part of the direction so that could be we're going to multiply that by the cosine yep and that there will be the x part so let's pop that into x oh i forgot good lord this one is actually the steepness let's set that to scalar and i'll put it to something small there okay okay so that's giving us the x part and we can get the y part by doing this again but with y right yeah all right let's go back to our oh wait so here's our sine wave let's duplicate this and we'll put the gerstner wave function in there instead okay let's see all right so we've still got the world position offset working the normals are now going to be broken because the normals have to change a bit okay so that's the world position offset and it just it's really hard to have this not become a mess given like all the things are intertwined um the real way to make this not a mess is to do it in the custom node but um anyway so let's look at our normals again so if you look at the equation for them here's the x normal the y normal and the z normal and we'll have to do a little ahead calculation here so let's just kill this so the normal for x is going to be wa so that's the radial frequency times the amplitude so multiply that times the amplitude that is going to be called w a okay the cosine fed with the normal stuff which we have actually up here already is going to be called c and the sine fed with the normal stuff is going to be called s like that so now we can go ahead and directly construct these guys so the x and the y are all negative so let's go ahead and just multiply by a negative one for them then we're going to multiply by oh yeah so we got w a times -1 times cosine the c which is up here yeah so this is oh my god do the comment all right this is wa the only thing we're missing is the things that converted into the x and y from the directional thing again so actually we have d dot x here right let's move that back here we've got d dot y here so we can go ahead and multiply this by d dot x and that is our normal in the x now if we multiply this by our d dot y we'll get the y version okay so now the z one is a little different because it's a one minus the sum of all of them so we'll construct it slightly differently so it's wa times s instead wherever s is there's s that's w a s times q i the uppercase one all right all right there we go so now if we go back here the normal actually is wrong a bit because it needs to be one minus on the z and you'll see the reason why i'm doing it this way in a bit because if we want to combine multiple waves this is the only way to do it right so this normal is going to be the x the y and then 1 minus this there we go oh that might help there we go okay so we now have normals we've got one wave we've got world position offset let's create a material instance and let's put this plane in the world somewhere there we go so we put the gerstener waves on it let's go ahead and save all okay so let's take a look at our material instance just close all of these so if we change the speed it will move which is good change the steepness you see you can see the steepness in there that needs to be kept pretty small see the wavelength we can make it two you know instead of one there's only one wave right now we can change the amplitude right make it smaller make it larger if you set the steepness to zero it will zero out the x and y movement or it should anyway and you can also change the direction of the wave and since this is normalized like we can set this to 1 1 even though that's not a length of one vector and it will you know be at an angle like that yeah so you can see that's working why don't we go ahead and make it look a little better so if we go into the gerstner wave material we can change the shading model to single layer water right so you have to add a single layer water material input and we're going to need some absorption coefficients and i just kind of like to divide this down by a hundred or something because it's a very small numbers that starts work that start working um also we've got some other controls now so let's go ahead and just make a base color let's make this right we just want to have controls for all these things now okay i'll set some ranges for those guys there we go actually let's set this to something let's set this to something too okay so now you can see we have water that's clear so if we go back to our gerstner waves here and adjust the opacity and if you turn this up you'll see more of the base color that's what that does so at zero you don't see any base color and if you change the absorption coefficients you get more absorption as the light passes through but you gotta remember absorption is kind of like the opposite right like it's absorbing the opposite of this color so if you see i'm on this side you're going to see blue right like if i go over to red you're going to see that lighter blue purple you're going to see green so go to the opposite side of the color wheel of what color you want to see right and refraction is important so i know like real water or glass real water is like 1.33 but sometimes the refraction looks very crazy strong don't be afraid to do something like 1.03 so that you can see the waviness but it doesn't overpower you know okay so that's one gerstner wave let's make it so that you can have more than one right so what we need to do here is make multiple of these gerstner wave functions right and then combine them let's move this out so let's say we're going to have up to four waves so if i convert this to a parameter and i call this speeds then the red one could go to this first one right i could duplicate this and column wave lengths let's put like 128 64 32 16. right and we'll put the red one in there yeah let's see let's do steep steepnesses let's just set those all to 0.2 and then we're going to have amplitudes and as you're doing this you're probably thinking uh you know what if i want an arbitrary number of waves i mean obviously this limits you to some number of waves to do it this way i have not figured out how to do a whole lot of waves like if you wanted 100 different ways let's say without doing something like fft waves which is just a total different subject so we won't talk about that as of now maybe i'll learn that in the future okay so here's direction one and two we'll say right so create another append and direction one and two will go into there so the second one let's make it just like that now if we duplicate this guy we're going to get direction one two three four right we can make that one minus one minus you know i'm just arbitrarily kind of choosing um whoops i'm trying to duplicate this i keep hitting ctrl w because that's what you used to do in the old version of unreal engine and it is confusing me now all right so there we go so we should be set now to have up to four waves so let's duplicate that guy and this one is going to be the green channel right so green speed green wavelength it's going to be the second well i didn't mean to do that second direction number of waves is always going to be the same let's go ahead and set that to 4. put that out here steepness is going to be the green steepness going to be the green amplitude so there's two waves here's our third one it's going to be blue blue speed blue wavelength blue steepnesses blue amplitudes for this direction and then our final one which will be the alpha so alpha speeds alpha wavelengths alpha steepness alpha amplitude final direction all right so now we have four gerstner waves that we can add together so how do we combine them right well the world position offsets can just add if we let's see let's go back to the the cursor part yeah there you go look they're just added together this x we're going to ignore that because we're talking about a world position offset so we don't want to add the x to it so let's just add these all together there we go okay so far as the normals go they'll add together in their own fashion here so um actually the x and the y's add but the z does one minus the add so if we add them together i failed i added the two wrong ones together didn't i so those first two go into this one go into the third one go to the fourth one yeah all right so that's all the normals added together yeah we break out the float3 components because the x and y just are added together and the z needs to be one minus wow so let's go ahead and take a look at this huh that's pretty heinous looking all right so let's let's set the amplitude of all the waves except for the first one to zero so we can just see one and let's set its speed so that we got something happening right okay now let's turn on let's reduce its amplitude all right let's turn on the second wave uh oh it has no direction it has to have a direction let's see okay so you see that waves kind of that way now let's make it pretty small maybe and set some speed on it oh no if you do these even multiples you will get not like good looking things so let's make this one 128's okay let's make this one like 75 so there's two waves here that one's three waves four waves but anyway so you can well look at that one that one's pretty pretty gross looking like that it's because the wavelength is so short right but anyway this is how you do it to combine a bunch of different gerstener waves and the actual values to get like good-looking stuff is kind of an art but that's one way to get four or some arbitrary number i guess if you wanted to do 8 you could just like put what i did here into a material function and then duplicate it your base pass vertex shader would start getting crazy i would think this is pretty expensive material now 916 instructions single layer water is not cheap yep okay what if you want to make waves that are dripping like circular waves so that's the final thing we're going to talk about so circular waves they use that same sine formula but the direction vector is a little bit different all right so if we take that matrix function for sine wave let's make a circular wave one so the only difference here is that direction is changed so let's see here direction is now the difference in position x y and a center so let's call this center and we'll do a subtract of the world position and the center oh this is the xy world position and the xy center only and then we will normalize that and that will create our new direction so let's make a material so we can see this you let's do the unlit thing as usual just to test it at first and we're going to need our plane there you go so we have a circular wave now you can see so if this goes here see we have a circular wave it looks like a octopus or something doesn't it um the actual the normals will still work the normals calculation and the only thing that changed was direction so that will work so let's go ahead and set this to default bit oh wait i gotta make sure that our change in space normal is off too so defaultlet put that into normal i can visualize it a bit better there we go it's pretty cool looking all right so this would be good for like a rock falling in the water or something or a drip of water falling in the water over and over except it's going the wrong way and it doesn't ever mask it's not like masked off so let's go ahead and fix a couple of those small things so the first thing we could do is reverse time and the reason we want to do that is so that it looks like it's coming out from the center right that looks better then the other thing we could do is the amplitude here we can multiply this by something to make it fall off right over a distance so we want the distance from the center let's find distance there it is okay so we take the distance from the center to the xy world position right let's divide that distance by a scalar we will call falloff distance let's say 64. okay saturate that which clamps it between zero and one then so if we preview this let me get the plane again there you go let's do a one minus there you go so you can see here is where it's going to be affected right let's let's take a power so we'll call this let's just set that to one there we go so now if you adjust the falloff power you know it will cause it to to fall off faster or slower all right so if we take that and we multiply that by the amplitude and take that result as the amplitude now we're cooking with gas let's see fall off distance there we go so you can see it's getting smaller as it gets away from the center we can adjust the falloff distance and power to make it so that it doesn't the ripples don't reach the edge if that something you want center has an interesting little dollop there it's probably where it's trying to normalize zero zero zero i have to figure out how to fix that sometime but you can see this is how you would do that and i believe that's it for now so if you have any questions on how to make waves or how to look at this stuff let me know if you know better ways of doing any of this definitely let me know if you know how to make the water look better let me know i didn't do it in this tutorial but you can put obviously texture normals that pan on the top and so on i didn't want to do that yet and i'm aware of that but there's a lot of cool tricks you could do with that and refraction distance fields to show ripples there's all kinds of cool stuff you can do but i'm going to stop here for now because the main thing i wanted to show was the math i hope you enjoyed thanks
Info
Channel: Stephen Maloney
Views: 6,921
Rating: undefined out of 5
Keywords:
Id: P52CvREt13M
Channel Id: undefined
Length: 67min 4sec (4024 seconds)
Published: Sun Sep 11 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.