chocola-mint / ChocoWater

2.5D Dynamic Reflective Water system for Unity's Universal Rendering Pipeline (URP).

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ChocoWater

日本語版

choco_water.mp4

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

  • This project is developed using Unity 2021.3.30f1, but should work with version 2021.3 and above in general.
  • You'll also need to install the Universal Render Pipeline package.
    • The easiest way is to just create your project with the 2D URP or 3D URP template.
  • At the moment, ChocoWater's rendering requires the target platform to support single-channel float textures (also known as R32F).

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.

Property Type Description
DisplacementMap Texture2D A 1D texture (Height=1) that describes the world-space surface displacement. Automatically created and updated by WaterVolume.
ObjectSize Vector2 The water's size in object space. Automatically assigned by WaterVolume.
WaterColor Color The base color of the water.
DepthColor Color A color used to darken the color of the water near the bottom. Uses multiplicative blending.
SurfaceViewDepth Float The desired size of the top-facing side of the water, in world space.
Wave Depth Propagation Ratio Float How 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 Depth Float How "deep" the topmost white edge should go.
Surface Foam Wave Scale Float Used to scale the wavy pattern that makes up the surface foam.
Surface Foam Wave Frequency Float How quickly the surface foam's pattern should be played.
Surface Foam Wave Depth Float The maximum additional depth added to the surface foam line.
Ripple Distortion Intensity Float How harshly the water ripples should be distorted.
Ripple Depth Propagation Ratio Float A 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 Frequency Float How frequently should underwater pixels be distorted.
Underwater Flow Intensity Float How strong the underwater distortion should be, in screen space.
Surface Flow Frequency Float How frequently should surface pixels be distorted.
Surface Flow Intensity Float How 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).

  • We can't use the Camera Opaque Texture provided by URP Cameras, because sprites are drawn as transparent meshes.

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:

  • Screen-Space Reflection is implemented by reading from the aforementioned _CWScreenColor texture and using the water surface as the axis of reflection. To hide artifacts caused by sampling out-of-screen pixels, the reflection fades out near the reflection limits, and pixels after that show the side view of the water instead. This behavior can also be overridden to maintain the water's view depth for as long as the surface may contain the screen reflection.
  • Refraction caused by the flowing water is faked using noise-based distortions sampling _CWScreenColor.
  • Wave foam is computed via distance from the water surface. For pixels where the wave displacement is above zero, a fake "splash ring" is also added with similar noise-based distortions as above.

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 moves every spring vertically according to its velocity.
  • It then updates every spring's velocity according to its displacement and velocity. This is what makes the spring feel like a spring.
  • Afterwards, every spring spreads its velocity to its immediate neighbors, according to their height differences. This is what makes waves propagate along the entire water surface.

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).

About

2.5D Dynamic Reflective Water system for Unity's Universal Rendering Pipeline (URP).

License:MIT License


Languages

Language:C# 100.0%