Home

Awesome

ChocoWater

日本語版

https://github.com/chocola-mint/ChocoWater/assets/56677134/4f22b11e-9a71-483a-82d0-2b869f2847a8

WebGL Demo

2.5D Dynamic Reflective Water system for Unity's Universal Rendering Pipeline (URP). Tested with both the URP 2D Renderer and Universal Forward Renderer. This package does not depend on compute shaders, and should run pretty much everywhere. Notably, it supports WebGL.

The code and shaders have been thoroughly commented, and every inspector field comes with a tooltip. Hopefully it will be helpful to people interested in implementation of these kinds of systems.

Inspired by ruccho's WaterRW and this article by Illham Effendi.

Requirements

Installation

ChocoWater is distributed as a git package. Use Unity's Package Manager and install using this repository's URL: https://github.com/chocola-mint/ChocoWater.git.

Once installed, you'll want to add the ChocoWaterRenderFeature to your Renderer 2D asset, and change the Post Transparent Layer Mask to contain only the layer where the water object is going to reside. For example, you may use the built-in "Water" layer. image

Finally, add the prefab ChocoWater by right-clicking the scene hierarchy and selecting "ChocoWater", and enter play mode to see the water rendered.

Material Properties

ChocoWater uses the shader Shader Graphs/Water Surface. Properties in italics are automatically assigned, you do not need to assign them yourself.

Note that the built-in Prefab comes with a material using the same shader, but you're recommended to make your own copy and edit its properties as needed.

PropertyTypeDescription
DisplacementMapTexture2D A 1D texture (Height=1) that describes the world-space surface displacement. Automatically created and updated by WaterVolume.
ObjectSizeVector2The water's size in object space. Automatically assigned by WaterVolume.
WaterColorColorThe base color of the water.
DepthColorColorA color used to darken the color of the water near the bottom. Uses multiplicative blending.
SurfaceViewDepthFloatThe desired size of the top-facing side of the water, in world space.
Wave Depth Propagation RatioFloatHow much of the displacement should be applied to the edge of the water that's "closer" to the camera. Setting this to 1 will make waves look like ribbons.
Surface Foam DepthFloatHow "deep" the topmost white edge should go.
Surface Foam Wave ScaleFloatUsed to scale the wavy pattern that makes up the surface foam.
Surface Foam Wave FrequencyFloatHow quickly the surface foam's pattern should be played.
Surface Foam Wave DepthFloatThe maximum additional depth added to the surface foam line.
Ripple Distortion IntensityFloatHow harshly the water ripples should be distorted.
Ripple Depth Propagation RatioFloatA value of 1 causes ripples to cross the water surface completely, while a value of 0 stops it from propagating at all. Somewhere around 0.75 should be fine.
Underwater Flow FrequencyFloatHow frequently should underwater pixels be distorted.
Underwater Flow IntensityFloatHow strong the underwater distortion should be, in screen space.
Surface Flow FrequencyFloatHow frequently should surface pixels be distorted.
Surface Flow IntensityFloatHow strong the surface distortion should be in, in screen space.

Technical Details

Rendering

A water mesh is generated by the WaterVolume component, subdivided according to the renderResolution property. WaterVolume also maintains a list of springs scattered evenly on the water's surface and runs physics simulation on Fixed Updates, and uploads their vertical displacements as a scalar (RFloat) 1D texture to the GPU. This texture is sampled through a bilinear filter. Note that the number of springs isn't necessarily equal to the number of subdivisions on the water mesh.

To draw the water correctly, a Render Feature called ChocoWaterRenderFeature is used. This render feature takes the screen output of the URP renderer and copies it to a global texture (by default, _CWScreenColor), accessible by the Water Surface shader graph. The Render Feature then draws "post-transparent" objects again, according to the specified layer mask (which should include the WaterVolume objects).

In the vertex shader, a displacement texture representing the list of springs on the surface is used to move the upper vertices into the correct positions.

In the fragment shader:

Physics Simulation

As mentioned above, the WaterVolume component maintains a chain of springs on the water surface. The simulation ticks every FixedUpdate and its speed can be controlled via Time.timeScale. Every spring is represented by a displacement and a velocity, and so WaterVolume allocates two float buffers for the simulation.

For each physics step (WaterVolume.Step()):

WaterVolume provides a method WaterVolume.SurfaceImpact() to interact with the water surface physically. If the water simulation is "predictable" in the sense that, you know ahead of time when and how objects will fall into the water, you can invoke this method manually. There are several simulation parameters that you can adjust to make the water behave more differently, but be careful as some configurations may cause the simulation to become unstable and diverge. Please read the tooltips carefully when customizing.

The WaterTrigger component is also provided here to support automatic interactions with rigidbodies from Unity 2D Physics.

WaterTrigger also handles buoyancy according to the state of WaterVolume. Part of this is implemented by Unity's own Buoyancy Effector, which can also be adjusted separately to add flows and underwater damping. WaterTrigger will also add an upward force near each wave, making objects float along waves.

Note that WaterTrigger is in no way physically-accurate, as computing the exact submerged volume of each rigidbody would be way too computationally extensive.

License

MIT License. But, please note that ChocoWaterRenderFeature is modified from DMeville's RefractedTransparentRenderPass (credited appropriately inside the same file).