I wanted to see if I could pull this off without dynamically regenerating the mesh for the waterfall every frame. It turns out, there is a way. :D
Each object that can block the waterfall (objects bearing a
WaterCatcher script in my prototype) has an outline mesh wrapping around its perimeter. (This can be auto-generated in advance using the collider's shape)
This outline mesh renders the water flowing along the object. I use a shader to
clip out the part that's beneath the object. I also track a left and right "catch" point where a waterfall lands on the object and flows left or right respectively, so I can
clip out the part that's to the left of the right waterfall and to the right of the left waterfall.
Then the vertical falls are just basic quad primitives, stretched to the appropriate length. I use another shader to scroll the waterfall texture over the falls and fade it out at the top & bottom ends. Then I layer on a foam particle system at the impact point to help cover the blend.
Here's a close-up so you can see the component parts.
At the top I have a "root" waterfall to kick things off. Each frame, after all the
Update() scripts have run to move things around, it fires a
CircleCast downward, to see if its water hits anything. If it hits a
WaterCatcher, it tells it to show its water skin downstream of the hit point.
I determine "downstream" using the hit normal - if it's very close to vertical, or if the incoming waterfall spans edges that slope in both directions, then we spill both left and right.
WaterCatcher has a left and a right waterfall of its own, which it enables and positions on its far edge if it's spilling in that direction - otherwise they stay hidden. These waterfalls in turn fire
CircleCasts downward to find what they spill onto, and so on...
The prototype still has a few visual glitches that could be improved - the water flow along an object pops on all at once instead of animating, and the flow rules could use a little extra tolerance or hysteresis so it doesn't cut off as easily on rotating objects. I think these should be fairly solvable issues though.
Background,rocks, and rotating platform textures via Kenney
Here are the tricks I use in my water catcher fragment shader:
// My wraparound geometry is build so the "x+" UV direction
// points "outward" from the object.
// Using derivatives, I can turn this into a vector in screen space.
// We'll use this below to clip out water hanging off the bottom.
float2 outward = float2(ddx(i.uv.x), ddy(i.uv.x));
// i.worldX is the worldspace x position of this fragment
// (interpolated from the vertex shader)
// _LeftX is a material property representing the worldspace x coordinate
// of the rightmost water flow that's spilling left,
// and _RightX the wold x of the leftmost water flow that's spilling right.
float left = _LeftX - i.worldX; // +ve if we're to the left of a left spill.
float right = i.worldX - _RightX; // +ve if we're to the right of a right spill.
float limit = max(left, right); // +ve if we're in the path of either flow.
// If the "outward" vector is pointing down, make this negative.
limit = min(limit, outward.y + 0.001f);
// If any of the conditions above make limit <= 0, abort this fragment.
// Otherwise, scroll the water texture!
// Counter-clockwise if we're in the left flow, clockwise otherwise.
i.uv.y -= sign(left) * _Time.y;