JONSWAP Ocean | Liquid

Summary

This is a basic liquid simulation using the JONSWAP ocean spectrum density model inside Roblox Studio written in Lua(u). This demonstrates multiple simulation modes using SkinnedMeshParts, EditableMeshParts, and EditableImages to simulate wave displacement and caustics. Its all completely open-sourced so go ham

Link to the Github Repository

Examples

Skinned Mesh Ocean

Skinned Mesh Ocean

Skinned Mesh Ocean Close-up

Skinned Mesh Ocean Close-up

Editable Mesh Ocean

Editable Mesh Ocean

Upscaled Skinned Mesh Ocean

Upscaled Skinned Mesh Ocean

Water Caustics Day/Night Cycle

Water Caustics Day/Night Cycle

If you’d like to try it live or open it in studio it’s all open-sourced under the MIT liscense. Have at it!
This is the Roblox Page

Information

I had initially structured this as half a personal project as well as a system for a game I intend on making, however after diving as far as I could into it, its difficult to say its properly possible.
While yes this looks great and you can implement LOD systems, theres a boundary between scraping for performant systems and something actually worth the effort. Not shockingly this lands much closer to the “not worth the effort” end of it all.
The ideal would be having most of the texturing coloring and foam mapping done via a PBR or shader of some kind but this was designed for Roblox Studio and it simply doesn’t allow that at the moment.

In the making of this I made

  1. Two vital components:
  • JONSWAP.lua module for creating a jonswap “wave spectrum” and sampler.

  • LiquidActor.lua module for creating actors to compute and handle the Meshes bones/vertices, and colorMap.

  1. Two straight forward utility modules:

Alongside some other junk

What to do from here

If you’re like me and still love the idea of all of this after knowing how frankly stupid it is to try and run it in Studio without shaders or real materials then heres where you could start studying where I dont think I will:

  • Foam textures are absolutely possible but its gonna require a lot of work from this point. It likely has to be done with EditableImages, any other way would surprise me but you can either have your color map read a texture based off height. Thats completely reasonable just complicated. If you plan on doing this I highly recommend starting from the pretty simple PixelMap module I made. If you dont know how it or EditableImages works, its pretty straight forward and reading the module should give you enough insight on it. Warning though; I’m like 90% sure that EditableImages start from the Top-Left instead of Bottom-Left causing y-axis inversion. I got around it twice in pretty similar ways, just expect something like that to happen as youre working with it. Long explanation short I recommend either 2 textures: 1 color, 1 foam texture definition, or mapping a texture to the color points based off of normalized height ratio

  • Foam spray, this is also absolutely possible, and a LOT simpler. It should just be a particle system that goes off based off when waves overlap. These overlap at a certain amplitude so mess with the scale some and see it happen. Acerola goes into good depth on this exact thing regarding Jacobian : Wave relation (based off a Sea of Thieves detailed discussion. Which is also super interesting!!)

  • Proper Tiling and fixing potential seams. The way I have it structured, each actor runs independently off RenderStepped. Its straight forward and gets what I need it to done. However I believe due to the potential desync of the updates based off of computation/setting costs it occassionally creates a tear or deformation. All the lazy attempts I made at solving this lapsed after creating and staring at a non-tearing seam with zombie eyes.

  • LOD! This is super important. TLDR: There are tons and tons of different level of detail tricks you could ABSOLUTELY do and I’ve done previously (just not implemented here). Past everything you should do, also consider lowering the active waveCount in the jonswap sampling based off of your LOD system, it’ll greatly increase performance.

  • Dynamic caustics would be very interesting, from IcyMonster’s example I approached it using a transparency based texture and got good results. Caustics get just as complicated mechanically as the water itself, because to make it look natural you have to think in great great depth. For example: How does each light source influence the color at this position? How does the distance between the point its cast and the distruption from the water affect the light level? How should the brightness of the mesh/part the caustics are cast on be totally affected based off water depth? I know its a bit silly to think about this stuff but it GREATLY affects the end product. This is something that could be extremely interesting depending on how you approach it yourself!

Stating anymore would be overkill, but please please please, if you want to learn more, discuss, or attempt any of this: reach out! Reply, make a git pull request, or message me on Discord. Discussion is the best way we can learn and develop theses kinds of systems

References

Icy-Monster. (2024). Phillips-Ocean. GitHub. GitHub - Icy-Monster/Phillips-Ocean: Lua(u) implementation of the Pierson–Moskowitz ocean spectra

Acerola. (2023). I Tried Simulating The Entire Ocean. https://youtu.be/yPfagLeUa7k

29 Likes

WHOAAAA THIS IS CRAZZZYYY!!! THANK YOU!! the one piece/sea of thieves inspired games that could be made with this new ocean system would be absolutely insane

I’m planning to keep updating this as I work on it! My current version already has a few of the systems I mentioned in the bottom chunk! It’s looking better and running better. Eventually it should honestly work for large scale bodies of water!

If there’s anything that stands out to you about it or any comments you’d like to make about the visuals please feel free to voice them; any input actually helps a ton
after staring at it for so long it gets harder to tell what looks good, what doesn’t, and what it could use lol

1 Like

Awesome!! Been trying to nail a realistic beach look for ages and your work really helped me get there.

External Media

Oh killer man!! I recommend tweaking the alpha a bit on those wave peaks to get just a tiny amount of the texture in or have some more easing on the the alpha gradient ! Looks great keep it up!

1 Like

So, I was putting the sea into my main game and hit some pretty bad client-side lag, especially when StreamingEnabled was on and I had a bunch of those water planes with all their bones active in the Workspace. After a bit of digging, I learned that there’s this long-standing Roblox performance thing: apparently, if a skinned MeshPart isn’t directly inside its own Model instance when it’s sitting in Workspace, the engine can end up treating the entire Workspace as its model, which then causes super expensive updates whenever anything changes in Workspace – like stuff streaming in or out.

1 Like

So, to tackle that lag, especially when stuff was streaming in, I updated my WaterController script. Now, instead of just having the skinned MeshPart water planes directly in a folder in Workspace, I make sure each one is inside its own Model instance. My controller now manages these Model, moving them from ReplicatedStorage to Workspace when they need to be active near the player and back when there not.

Ig it makes sense because WorldRoot inherits from Model, good to know

Hi,
Are you going to OS your updated version?

Thanks

It really depends! At bear minimum a majority of the techniques I’m going to use will be open-sourced alongside a demonstration.