Realistic Oceans Using Mesh Deformation!

How did you make the Plane with more vertices than 4? I made a whole thread about it but nothing worked?

1 Like

You have to make sure that the bones is rigged to the plane. Check out this tutorial for more details.

1 Like

So it’s not possible to add the bones on roblox in a for loop it needs to be done in blender?

Yeah, pretty much. Add the bones in blender, rig it there, then import it using the avatar importer (it allows for more tris)

Whys there a virus in the demo place?

1 Like

It was from an old plugin of mine, I have since removed it. You can just delete the script, I should probably update the demo place. However the virus doesn’t do anything, I have done toying around with it, it has stopped working. I reccomend you remove it, it was inserted by a plug-in of mine.

A simple delete should stop the virus, not sure what it does.

Very nice module! I think you could do a lot more with this than just oceans, for example moving trampolines… Or nets… Speaking of trampolines and nets, does the module provide any possibility to change the settings after the Wave object is created, such as gradually reducing the timemodifier or wavelength?

Yes! The :ConnectRenderStepped() method on a wave updates the wave every frame based on the wave settings. You can edit the properties on he wave object itself and it will update automatically. This weekend I plan on adding a function to update the wage’s WaveSettings to make what you describe easier to do.

1 Like

I’m having some trouble trying to add my own function into the module for finding the height of the water’s plane given a coordinate, i’d assume it’s just plugging in the target position into the gerstner function + account for perlin noise, but mine appears to be out of a sync the farther i get from a bone. Am I handling this in the right way? I suppose another way of solving this would be approximating height based on the closest surrounding bones, but I have no clue which method would be more efficient. Here’s what I have so far, it’s not much-but the main problem i’m trying to troubleshoot is the “de-sync”

function Wave:GetHeight(pos)
	local Settings = self._settings
	local Direction = Settings.Direction
	if Direction == EmptyVector2 then
		--account for noise (havn't done this yet, so I just set the Direction to,1) for the meantime )
		Direction = GetDirection(Settings,pos)
	return (Gerstner(pos,Settings.WaveLength,Direction,Settings.Steepness,Settings.Gravity,self._time).Y)

I barely got by in Pre-Cal 1, so pls b kind if i’m missing something obvious.

edit: I’m also just using a BodyPosition to position the character on the Y axis, and it seems responsive enough so i’m assuming it’s a fault with my implementation.


This is because the gernstner wave formula returns a x, y, and a component, meaning that the x and a components are not always the same. You are simply ignoring the x and z components, making it appear delayed because the corresponding y axis belongs in a different x and z position. I reccomend going with the second option. You might be able to use @Quenty ’s octree tree module to find closest bones. You might have to average the y positions in the bones. Hopefully Roblox will add physics and collision to support to bones and skinned meshes to make this process automated.

1 Like

Thank you very much for your reply, you definitely led me in the right direction. Although even after getting the mean average of heights of nearest bones-the accuracy is still a little off. I know I should probably do more than just average the heights, but i’m not sure what… I tried doing a weighted average by assigning low weights to farther bones, and then averaging them-but that failed as well. I really want to find a slightly more accurate way to approximate without increasing bone density. Any ideas?

Here’s what I’ve come up with so far

local RunService	     = game:GetService'RunService'
local ReplicatedStorage	 = game:GetService'ReplicatedStorage'

local Player		 = game.Players.LocalPlayer
local RootPart		 = Player.Character.HumanoidRootPart
local Plane			 = workspace:WaitForChild("Wave"):WaitForChild("Plane")

local BodyMover		 ='BodyPosition'
BodyMover.MaxForce	 =,math.huge,0)
BodyMover.D		     = 625
BodyMover.P		     = 20000
BodyMover.Parent	 = RootPart

local LoadCustomLibrary	 = require(ReplicatedStorage:WaitForChild("Nevermore"))
local Octree		     = LoadCustomLibrary("Octree")

local planeOctree = --no idea if i'm using the octree module correctly/efficiently, but it seems simple enough
for _,v in pairs(Plane:GetDescendants()) do
	if v:IsA("Bone") then

local sampleRadius	 = 100
local maxSamples	 = 3

	local closestObjs,distances = planeOctree:RadiusSearch(RootPart.Position,sampleRadius)
	local sum = 0
	--for _,v in pairs(closestObjs) do --unsure if this is less efficient or not, probably dependant on num of samples
	--	local pos = v.TransformedWorldCFrame.p
	--	sum = sum + pos.Y
	--local average = sum/#closestObjs
	local numSamples = math.clamp(#closestObjs,0,maxSamples)
	for i = 1,numSamples do
		local v	 = closestObjs[i]
		local pos = v.TransformedWorldCFrame.p
		sum = sum + (pos.Y)
	local average = sum/numSamples
	BodyMover.Position =,average,0)



Actually forget what I said about averaging, how about you use the Octree module and find the closest bone. As long as there are enough bones, it will be accurate enough.

1 Like
Out of curiosity, why does your sample file have a hidden backdoor in the Terrain instance?

I have already addressed this

I plan on re-adding the models without the virus


I have re-added the models with the virus removed. It was a fake Gui-to-Lua plugin I was using to make guis for plugins.

1 Like

Is there a way to run :ConnectRenderStepped() on variable frequency (1-60hz for example), depending on how far the character is? It lowers FPS a lot in my game.

You can go into the module’s code and go to the ConnectRenderStepped function. You can copy that function, change the name, and use whatever method you like to update the wave. In that function, I used RenderStepped. You can create a new method. If you want, you can add methods for other render events like Stepped and Heartbeat.

You can use a culling technique, where you put a group of bones in a table, and check it one of the bone’s position is in the camera view, and run that group. On average this reduces 30%-100% (usually 60%) of the amount of bones to update

1 Like

That is actually a really simple idea but I never thought of it. I’ll implement it.

Hello! First of all, thanks a million for the resource! I’ve been looking everywhere for how to do this! I think it’s super cool that roblox has developed technology that even allows for skinned mesh oceans, so I’m super excited to see how far you can go.

I’m in the works of making a Pirate game. I want to use a skinned mesh ocean for it. The map is far too big to place them in manually (About 30k x 28k studs) I’m pretty sure that many bones would crash even Nasa computers haha. I’m really not too great at scripting, so I’m wondering,

How would I be able to modify the system to detect planes anywhere in the workspace? I want to change it to generate infinitely.* In the demonstration, every plane is connected manually. It works fine, but when I try to run a loop to detect new instances or use a function to do the same, I get errors. The big one seems to be that in the module that controls the waves, there’s a loop that collects information from each of the planes.
Any help would be massively appreciated! :smile:

Well you could weld the ocean to the character, only problem with that is the PBR texture will follow the player too, giving away the affect. I think what you would be looking for is manually placed waves, and you may want to do some modifying of my code for optimizations. For example, you can hide waves that is a certain distance away from you and use the MaxRenderDistance to make the waves stop moving when a certain distance away. Also, for performance, laying out the ocean (although it may seem tedious) is the best way to accomplish what you are trying. You could alternatively clone the waves as you go, but would cause lag as cloning is not efficient. Having all waves placed when the player joins is the best solution, and hiding them when a certain distance away is also good. To hide the fact you are making the waves invisible, you can use atmosphere affects to limit the player’s render distance with a seemless edge.