Sunday, September 3, 2023

Basement Water, Improved

This is a short update to my previous post. I've found a way to add most of the features that were missing from my previous basement room water.

Walls Blocking Waves

One problem with my water implementation is that the ripples/waves pass straight through walls because the fragment shader that draws the water doesn't know where the walls are. Let's fix that. I don't want to add a strict line-of-sight test for every point in the water to the source of the splash because that's unrealistic and difficult. Waves can pass through narrow gaps and expand outward; trying to clip them to the exact walls looks incorrect. Instead, I can calculate the extents in each of the four cardinal directions (+X, -X, +Y, -Y) and pass them to the shader as planes bounding the waves for each splash. This is done by casting a number of rays (I used 90) out in each direction from the splash origin, clipping them to the building walls, and tracking the max distance a ray can travel in each of the four directions. This is similar to how room ceiling lights are bounded to optimize lighting in the fragment shader.

The effect is that any splash bounded on one side by a wall will have its waves blocked by the wall. This is particularly apparent for splashes made in enclosed rooms that shouldn't propagate outside the room. This appears to fix most of the obvious cases where waves went through walls.

Refraction

Adding refraction for underwater objects such as the floor requires modifying objects that were previously lit and drawn. Fortunately, I already have a system to read the current color buffer and pass it into the shader as a texture. If I then make the water fully opaque rather than transparent, I can manually blend the refraction into the background using the computed alpha (transparency) value. Now that I have the underwater part of the scene as an image, I can use the same texture coordinate domain warping approach I used with reflections to add refractions. This produces a nice wave distortion of the floor and other underwater objects under the ripples.

Caustics

Finally, it's time to add fake caustic lighting effects to the underwater objects. This is another color transform the fragment shader applies to add the effect to previously drawn geometry. Caustic lighting appears as bright and dark spots in areas where the rippled (non-planar) water surface acts as a lens to focus light onto certain areas below the water.

Most of the reference images you can find for water are taken in outdoor areas with the sun and a water body such as an ocean, lake, river, or swimming pool. My basement water is different for two reasons. First, the light source is a array of overhead rectangular area lights rather than a single high intensity light such as the sun. It's not clear exactly how to compute caustics efficiently in this case. Second, indoor water isn't subject to forces such as the wind and tide that create waves or smaller surface irregularities. Undisturbed indoor water is almost completely flat. When objects are dropped into the water, this creates ripples. The ripples from multiple nearby splashes merge and create interference patterns. Adding many splashes gives the water a very chaotic wave pattern.

I'm not entirely sure what the caustic lighting patterns on the floor should look like in this situation. So I'm going to do something simple and calculate the lighting from only the water surface normals that are used for reflections and refractions. This is simple but not physically correct. The only problem is that I can't simply adjust the normals because lighting has already been applied to the underwater objects. The best I can do is to adjust the color of the refracted pixels based on the signed wave height to increase or decrease their brightness. I had to turn the effect down so that it's not as obvious that this is a hack. I think it's still noticeable, and is the only observable effect of the ripples directly below the player.

Anyway, here is an updated video of basement water. I think the water effect looks much more realistic now.


 And here are some screenshots after I walked around in the water.

Basement water, now with refractions, caustics, and wall blockers.

The scene from above, but after moving backwards and waiting a few seconds.

In case you're curious what this looks like with the lights off, it's dark as expected:

Water shown in a dark basement with the lights off, and the only light coming through the stairs opening from the floor above.

Update

I just realized that I haven't enabled reflections in these screenshots. The zombie/people reflection code was also broken, and I had to fix it. Here are the new video and three updated screenshots with both reflections and refractions.






2 comments:

  1. Yes! Fixed refractions and simulated caustics!
    The most obvious issue at this point is the waves are point-only. If you could do limited linear waves, that would allow the more realistic "boat wake" kind of shape, with a semi-circular ripple at the back, two linear waves at an acute angle pointing in the direction of travel, and another semi-circular ripple at the front. Still not perfect, but much better.

    ReplyDelete
    Replies
    1. The refractions still aren't correct and have been further fixed on Sunday.

      Waves are modeled as a sum of sinc() function impulses added at the point where an object interacts with the water. I'm not sure what the proper way to model a linear wave is. Maybe point-to-line segment distance passed to sinc()?

      I don't currently have a way to track objects moving in a line because I don't have boats. Maybe it would work for the player and people in the building? If the water is deep the movement could be a line. Currently I have shallow water as the default, and the impulses represent individual footsteps.

      Delete