Totally agree that there's room to look more water-y (depending on your application's needs).
One thing that can be done is playing around with the numbers / uniforms in the fragment shader to change the look. For many use cases you can get pretty far with that without actually changing the implementation in this demo.
If a more physically based level of realism is needed (like the water ripples on click in the demo that you linked) you can use techniques like height simulation based on a wave equation [1].
You'll of course need to balance realism with realtime feasibility - but often times getting a much better look comes down to tweaking some numbers until things look good!
Because it's not solving for the water surface elevation and velocity, only for light reflection and refraction from a pre-set surface.
> The way that the water appear to move comes from slightly altering the point on the refraction and reflection textures that we sample from every frame.
>
> These slight offsets comes from sampling a du/dv map, which is just a texture that encodes different x and y offsets.
The app in the link you posted does indeed look like it's solving at least a shallow water system of equations, and looks very excited to me as a wave physicist.
I study the role of waves in the larger meteorological and oceanographic context, specifically (1) how they govern momentum and energy exchange between wind and ocean currents, and (2) how they contribute to Lagrangian surface transport of pollutants, think oil spills or microplastics.
I have always thought about getting into rust but since so many of projects are web focused its hard to integrate it into a project. This really makes me want to explore Rust!
There's a recent issue [1] in the wasm-bindgen repo that explains this, but essentially:
- In this demo the WebGLRenderingContext was created on the Rust side (technically by calling a light JS shim), but it could've just as well have been passed in from JS.
- This means that JS could very easily have had access to the WebGLRenderingContext.
- This means that we cannot guarantee that it is immutable. Who is to say that there isn't a line of JS `gl.mutated! = true;` that we don't know about? We have no guarantees that whoever instantiated this WebAssembly module isn't doing something funky.
- So we treat most DOM / JS APIs as if they have interior mutability, so more or less you can think of them as `RefCell`'s [2]
---
As an aside. When you work with WebGL you're just making calls to the GPU and your state on the GPU gets mutated. The object just controls state (WebGlRenderingContext) does not get mutated (aside from maybe a couple things that I'm forgetting..?).
As in.. `gl.viewport` doesn't actually mutate `gl`. But again.. just a small aside!
In general the idea here is that we can't guarantee that there is only one mutable reference so there is no point in calling these things `&mut`, even for things that really do mutate.
---
So yup! Interior mutability without runtime checks for many of the APIs is "the right way" in today's idiomatic Rust + WASM + DOM access since you can't guarantee that there isn't foul play going on from whoever instantiated the module.
---
Does this cause issues? It hasn't (noticeably) for me yet but I'm only a couple months into using `web_sys` so I haven't worked with every single DOM API.. so grain of salt!
I'm not sure why rust and webassembly are significant here. All the webgl commands still need to be javascript and the shaders still need to be glsl. Almost nothing in a demo like this needs to be done on the cpu.
- I see that I poorly communicated this in the title, but I was actually shooting less for "Rust/WASM makes this better!!" and more for "Hey, this is possible with pretty much just Rust/WASM!" I ended up hand writing about 10 lines of JS [1] in total
- More minor point - WebGL commands don't need to be called via JS [2] after anyref and host bindings land, and I think that it's important for people to know that it's possible to do things on the web without JS (whether that's a good idea is very situational)!
So yeah apologies if the title could be a bit better - but the main goal was to share with others that it's possible to build 3d experiences on the web with Rust today!
And - just to not come off wrong here - I'm not saying whether this is right or wrong and when to go this route. I am just interested in demonstrating how to do it :)
But all in all you're right - all of this same experience could totally be built with only JS if someone so desired!
Such a weird comment... all the webgl commands in the article are clearly written in Rust - `gl.bind_framebuffer(GL::FRAMEBUFFER, framebuffer.as_ref());` - and that's only possible because of WebAssembly. How are they not significant?
That said, it's unnecessarily dismissive. It's still a cool project! And a useful resource for anyone who does need to use Rust with WebGL for whatever reason.
Miiiiinor point/caveat here but jotting it down just for others that might be less familiar!
Today WebGL is commonly referred to as a JS API because pretty much every WebGL app is written using JS.
But, even though that MDN link quite literally says "JavaScript API", it turns out that WebGL is entirely specified in WebIDL and isn't coupled to JS.
Because of this, `wasm-bindgen` [1] is able to auto-generate all of the WebGL bindings [2] that I used for this demo.
Today `wasm-bindgen` automatically generates some JS shims because that's the only way to call the WebGL APIs at this time, but once anyref and host bindings land you'll be able to interface with the WebGL APIs directly without going through JS.
`wasm-bindgen` already plans to just replace those JS shims with direct host bindings.
But yup - totally agree with your points here - just sharing this minor detail that will be more relevant in the future!
Yup! I'm actually working on a project that needs to use WebGL but can't use JS bindings for a quirky reason. So I'm actually super glad you posted this — it's a great resource. Thank you!