Making A Mesh Deformation Ocean

Well if you want crests you could always change your noise density :slight_smile:

1 Like

That wouldn’t make accurate crests, that would just increase the overall height. The whole point of crests are being fo it tie is quickly and crest. Using perlin noise would just make the waves taller, and that’s not what a crest is. :slight_smile:

3 Likes

Thanks, super useful to know! I will definitely be using.

2 Likes

A lot of people need some assistance with the wave part, so here:
I’m not too great with scripting, so any direct scripting advice I give will actually probably be a detriment even if it makes your ocean have individual waves.

I can suggest searching “sine wave” in the roblox library and see what some of those use for their formula. You could get some ideas by tinkering with different scripts like I did.

The general idea is that you want to make each bone have a unique Y value. You do this by making the Y value dependent on the bone’s X and Z values if that makes sense.

You can look at this equation for a simple sine wave.
image

y is the Y value of any point
x is the X value of any point

'a' is the amplitude of the wave. That basically means how high and low the crests and troughs are

392f6207e8f19f0f1da99fcce1d257e0

'b' is the frequency of the wave. That basically means how close each peak and valley is to each other.

'c' is a modifier similar to "time". You change the C value and the wave moves. (its more complicated but that's essentially how you can look at it)

All you do is place any number you want for x,b,c and you get what y should be.
Since you want to make an ocean out of it, you want y to be constantly moving in a consistent and predictable way.

X will be the X value of the bone (and make room for Z, being the Z value of the bone)
A, the amplitude, will be whatever you want (good for weather conditions)
B, the frequency, will be whatever you want (good for weather conditions)
C will be time. tick() or os.time() works, but again they aren’t synchronized between clients, so different players will see the wave at different times which ruins the look of collisions.

You get the Y value and plug that into the bone’s Y value and you have a waving ocean.


Also bear in mind that this is a basic sine wave formula and not a Gerstner wave formula. I haven’t actually done a gerstner wave formula if you can see in my original finished product gif. If anyone can help me with that, that would be great.

7 Likes

Thanks. I have tried some other formula to create my waves but this one is the superior.

1 Like

I considered writing my own version of a guide like this, but instead, I’ll just post a few tips:

  1. If you are using textures rather than a SurfaceAppearance, you can lock the water plane to the position of the camera and make it appear to move properly by scrolling the UV offset of the texture.

  2. Use the same formula that you used to generate the water to detect surface collisions by sampling the wave at the target position rather than trying to detect collisions with the actual wave mesh.

Here is a Gerstner wave function you can use to generate your waves:

--GerstnerWave(Vector3 SamplePosition, Float Wavelength, Vector2 Direction, Float Steepness, Float Gravity, Float SampleTick)
function GerstnerWave(SamplePosition,Wavelength,Direction,Steepness,Gravity,SampleTick)
	local k = (2 * math.pi) / Wavelength
	local a = Steepness/k
	local d = Direction.Unit
	local c = math.sqrt(Gravity / k)
	local f = k * d:Dot(Vector2.new(SamplePosition.X,SamplePosition.Z)) - c * SampleTick
	local cosF = math.cos(f)

	--Displacement Vectors
	local dX = (d.X * (a * cosF))
	local dY = a * math.sin(f)
	local dZ = ( d.Y * (a * cosF))
	return Vector3.new(dX,dY,dZ)
end
  1. The wavelength of your smallest wave must be larger than the distance between vertices on your water mesh, otherwise, it will look pretty strange.

  2. You can add the displacement from multiple Gerstner waves to get a more convincing final displacement for the water.

26 Likes

OMG IVE BEEN LOOKING FOR SOMETHING LIKE THIS FOR SO LONG!!! Yay

2 Likes

I can’t tell you how much this is amazing timing for me. Due to that being a script imported from blender, would it possible for this to become a model?

3 Likes

Yeah, I could make the ocean mesh a model and link it in the original post.

2 Likes

Hi, I’m having difficulties trying to get this function to work. Could you provide a code snippet, or explain what type each variable has to be?
Thank you.

2 Likes

GerstnerWave(Vector3 SamplePosition, Float Wavelength, Vector2 Direction, Float Steepness, Float Gravity, Float SampleTick)

SamplePosition is the position at which you are checking the wave displacement (E.G. the world position of a given bone in a water mesh)

Wavelength, Direction, Steepness, and Gravity are all pretty self-explanatory, but it does take some tinkering to find numbers that look nice.

SampleTick is the time you are sampling the wave at, should be something that you can sync between clients if possible. Waves sampled at the same SampleTick with the same arguments will always return the same results (deterministic).

Here are some example arguments that I’ve found look pretty nice:

	local Wave1 = GerstnerWave(SamplePosition,80,Vector2.new(1,0),.05,1.5,SampleTick)
	local Wave2 = GerstnerWave(SamplePosition,90,Vector2.new(0,.3),.07,1.5,SampleTick)
	local Wave3 = GerstnerWave(SamplePosition,100,Vector2.new(1,1),.05,1.5,SampleTick)
	local TotalDisplacement = Wave1+Wave2+Wave3 -- This is your final wave displacement
2 Likes

Hello again, I am struggling to to import the FBX into Studio. Would you mind exporting this as a Roblox model?

Thanks

https://www.roblox.com/library/6580905134/FBXImportGeneric

try that out

3 Likes

Wow, this can help alot I appreciate it!

Thank you for the clarification.
I’m currently doing this inside of a loop to update all the bones.

for _, bone in pairs(plane:GetChildren()) do
	if bone:IsA("Bone") then
		local SamplePosition = bone.Position
		local SampleTick = tick()

		local Wave1 = GerstnerWave(SamplePosition, 80, Vector2.new(1, 0), .05, 1.5, SampleTick)
		local Wave2 = GerstnerWave(SamplePosition, 90, Vector2.new(0, .3), .07, 1.5, SampleTick)
		local Wave3 = GerstnerWave(SamplePosition, 100, Vector2.new(1, 1), .05, 1.5, SampleTick)
		local TotalDisplacement = Wave1 + Wave2 + Wave3 -- This is your final wave displacement
		bone.Position = TotalDisplacement
	end
end

It doesn’t work however. Is there another way to set a bone’s position?

You’ll want to sample the wave at the WorldPosition of the bone instead of the Position. That could be the cause of your issue.

1 Like

Something like this?

for _, bone in pairs(plane:GetChildren()) do
	if bone:IsA("Bone") then
		local SamplePosition = bone.WorldPosition
		local SampleTick = tick()

		local Wave1 = GerstnerWave(SamplePosition, 80, Vector2.new(1, 0), .05, 1.5, SampleTick)
		local Wave2 = GerstnerWave(SamplePosition, 90, Vector2.new(0, .3), .07, 1.5, SampleTick)
		local Wave3 = GerstnerWave(SamplePosition, 100, Vector2.new(1, 1), .05, 1.5, SampleTick)
		local TotalDisplacement = Wave1 + Wave2 + Wave3
		bone.Position = TotalDisplacement
	end
end

This doesn’t seem to work either…

You also need to cache the initial position of the bone, the GerstnerWave function is returning a displacement that is meant to be added to a position.

Something like:

bone.Position = OriginalPosition+TotalDisplacement
1 Like

just to clarify, would this be the final product?

for _, bone in pairs(plane:GetChildren()) do
	if bone:IsA("Bone") then
		local SamplePosition = bone.WorldPosition
		local SampleTick = tick()
		
		local Wave1 = GerstnerWave(SamplePosition, 80, Vector2.new(1, 0), .05, 1.5, SampleTick)
		local Wave2 = GerstnerWave(SamplePosition, 90, Vector2.new(0, .3), .07, 1.5, SampleTick)
		local Wave3 = GerstnerWave(SamplePosition, 100, Vector2.new(1, 1), .05, 1.5, SampleTick)
		local TotalDisplacement = Wave1 + Wave2 + Wave3
		local OriginalPosition = bone.Position
		bone.Position = OriginalPosition+TotalDisplacement
	end
end

No, if you do that you are changing the OriginalPosition of each bone each step. You’ll want to store (cache) the original position of each bone in a table at the start and get it from there. You could probably also use Bone.Transform, but I haven’t experimented with that yet.