Performant skinned mesh ocean with built-in support for floatable objects (boats, ships and more!)

About
A few days ago I saw a post on the devforum about an endless PBR Skinned Mesh ocean so I decided to try and research the topic further since it seemed like a fun challenge and after a few sleepless nights I am finally ready to release it.

Features

  • High performance (~0.05 ms with 9 planes (530 bones each))

  • Synchronized between clients using “GetServerTimeNow”

  • A fast and accurate height lookup function for any XZ coordinate

  • Built-in floatables support for stuff like boats, ships etc.

  • Automatic bone scaling at a distance so it smoothly blends into any bones outside of render distance

  • Customizable performance settings (render distance, floatables render distance, floatables resolution drop off distance*)

“floatables resolution drop off distance” is the distance at which the system will automatically switch to only using the center point of a floatable object for height lookup

So what does it look like ?
Glad you asked! Here are some videos + performance benchmarks:

6 boats with 2 lookup points each (~1.1 ms total simulation time)

12 boats (~1.7 ms total simulation time)

Tutorial on creating floatables
Creating a floatable object is pretty simple you just need a platform with two attachment (front and back which will be used to determine the height) and a body.
image

The platform is anchored and doesn’t move it’s basically the HumanoidRootPart of a character (also the PrimaryPart of the main model), it is used to position the body in the world and it is required that you keep it at the same height as your ocean.

The body is what contains your boat/ship whatever you want to use, it must have a part named Primary and it’s PrimaryPart must be set to it.

And that’s pretty much how you set one up, you can also remove the attachments if you want the height lookup to be performed at the center of the Platform.

API

WaveModule.Enable(IsEnabled: boolean) (Client Only)
Toggles the simulation

WaveModule.SetSetting(Setting: string, Value: number | boolean)
List of available settings:

  • Debug (boolean) - Debug prints (grid creation, simulation time etc.)
  • RenderDistance (number) - The maximum render distance
  • FloatablesRenderDistance (number) - The maximum distance for floatables simulation
  • FloatablesDropOffDistance (number) - The distance at which the module changes to only using the center of the floatable for height lookup

WaveModule.GetHeightAtXZ(Position: Vector2 | Vector3, CameraOrigin: Vector3) -> Height: number
Get the height of the ocean at XZ coordinates
Disclaimer: If you pass in position as a vector3 value it will be read as
X = Position.X
Y = Position.Z

Note: CameraOrigin is used on the server to simulate the scaling of bones at a distance which is present on the client, if you don’t provide it the calculation will be done with all bones at 0 distance from the ‘camera’

Download
You can do whatever you want with this resource, I do ask that you credit me but I don’t mind if you choose not to.
Skinned mesh ocean system.rbxl (184.6 KB)

Technical stuff
This part is for those of you interested in the inner workings of the module and how some of the features are achieved.

Starting off with the one thing I saw as the biggest problem people were having when it comes to Gerstner wave based oceans, the height lookup. I achieved what I call “pretty much perfect accuracy” by creating a grid of boxes that each contain 2 triangles.

Whenever a height lookup is performed the module gets the nearest “node” using an octree nearest neighbor search then it determines which triangle the XZ point lies in and finds the height of it inside that triangle, and that allows for “pretty much perfect accuracy” height lookups.

Another thing I did when trying to optimize the module further was to use Vector3 instead of Vector2 due to it being a native datatype which allows for better performance when indexing it’s axes and calling it’s functions. I also decided to ditch metatables for the grid systems and instead define the nodes as functions that I can call to get the height of a XZ coordinate in them.

Finish
This is my first community resource post so any feedback is appreciated! Also if any of you have any tips or tricks on how I can speed up the module even more I would love to hear them!

Credits

@Sir_Falstaff - For adapting the Gerstner Wave formula to roblox
Roblox engineers - For the atomic binding module used to track the floatables with streaming enabled

106 Likes

Wow! Thank you so much :heart:
This will help out a lot

4 Likes

Gerstner wave function isn’t mine, I just adapted it to use on roblox! :smile:

5 Likes

UPDATE 1

  • Added height lookup on the server
  • Fixed a bug with floatables being in the sky when switching to center point height lookups
  • Fixed a bug with floatables when the ocean isn’t at Y = 0
  • Added API documentation to the post
2 Likes

Well it still helped me save time on the project since the formula was already available as a function that I could add, so I still think you deserve the credit even if you didn’t make the formula itself

5 Likes

Is there a way to add more planes to make the ocean larger?

1 Like

I tested this out and I noticed a bug where if you put the ServerHeightSphere in a position towards the center, it will break and go to some weird coords.

I put the spheres position at (336.12, 1, 2.769) then ran and it immediately happened.

2 Likes

UPDATE 2

  • Reworked the grid generation (grid is now generated globally instead of per plane), this should fix the bug reported by @iiAllFightNoFlight
2 Likes

Yes you can just clone one of the meshes in the waves model (make sure it doesn’t overlap with the other meshes)

1 Like

I think I found a bug where if you use GetHeightAtXZ far from the origin ~2000 studs away, it will output very large heights instead of what the waves visually appear as. Can’t for the life of me figure out why this is occurring.

UPDATE 3

  • Fixed GetHeightAtXZ returning an incorrect height on the server after 10-15 seconds of the script (The server wave model was being deleted automatically due to it not being parented after being cloned) (Should fix the issue reported by @Fezezen)
  • WAVE_HEIGHT_POSITION will now update if the wave model is moved vertically
3 Likes

Is there a way to make boats or ships with this?

1 Like

Hi, i am starter dev and your sea helped me alot! But i have a problem with it. How do i can make float new childs that were added during experience in folder “Floatables”, Thank you!

Hi! You can just parent your floatable to the Floatables folder, as long as the floatable is setup correctly the script should automatically detect it.

3 Likes

but this dont work for me, maybe i do something wrong?

It looks like it was an issue with the code, I’ve updated the download file with a fix for this issue.

3 Likes

Pretty cool but the videos are a little confusing , I don’t really see any waves - it just looks like a flat water plane bobbing up and down

1 Like

Videos are pretty old, you can get a much better effect by playing around with the settings and getting a better texture, here’s an example from a game I’m working on:

3 Likes

Any way to have different wave settings depending on different wave parts?

1 Like

Hello, i just noticed that platform in floatable have locked Y position, how to disable this or how to change locked Y position value. Thank you