Creating Windy Water Surface Reflection Effect with Code
I was learning image manipulation with code when I encountered this topic: water reflection. If you take an image, mirror it vertically, then blur the mirrored image, you will get a simple water reflection effect. But that is if the water surface is perfectly still. What if there is wind?
Problem 1 - Deconstructing the Puzzle:
Wind introduces distortions to the reflection. The distortion is expressed by sampling each reflection pixel with a offset (to the location that directly mirrors the original image). I needed a function to describe this offset, which would prove difficult because the water reflection is complex.
Process: My first attempt was to have the function directly describe the visual phenomenon, by shifting the pixels left or right per line basis. Indeed, this is what a lot of Photoshopped water reflections look like. A zigzag pattern. However, this function is not at all robust, and the reflection looks conspicuously procedural.
I realized that if it was hard to reverse-engineer the appearance, I could try to engineer the process, by constructing the problem more physically.
Solution: A windy water surface is like an uneven mirror. I know how a single light ray interacts with a point on a mirror. If I could find out how to describe the mirror surface, then the surface could drive the sampling of the reflection. So the problem could be broken into two functions: one that describes the water surface, and one that calculates the sampling offsets based on the surface function.
Problem 2 - Creating a Water Surface Function:
Without the time for an in-depth research on physically accurate water, I must look for a cheap but aesthetically acceptable way to simulate water surface.
Process: A wave map is used to store the water surface information. The blue value of a pixel reflects the surface elevation at that point. The base of the waves is the sine function. My experiment started with a single Y-direction waveform function.
This function captures three main qualities of the waveform: 1) there is a base wavelength as an input; 2) the wavelength of each cycle is slightly randomized; 3) waves closer to the viewer have increasingly longer wavelengths, faking perspective depth.
In the real world, the complex look of water surfaces is the result of superposition of multiple waves with different directions and frequencies. The superposition principle provides an elegant solution to achieving visual complexity, as the net displacement of multiple waves is simply the algebraic sum of individual wave displacements.
Based on this, I expanded the function by superposing two Y-direction waveforms, each with a very different base wavelength.
Next, I generated two X-direction waveforms in the same way, then superposed them on the Y-direction waveforms. The result still didn't look as dynamic as I hoped. The superposition method has its limitation, as it appears to preserve a grid-like pattern.
I experimented with some tweaks and hacks, which was permissible given that my method already integrated artistic abstractions with physical principles.
Solution: Eventually, the method that yielded a decent result consists of three steps. Step 1 generates a four-time superposed wave map. Step 2 takes that map and inverts its elevation. Step 3 combines the original and the inverted by only preserving the higher elevation of the two maps at any given point.
The final surface function has four input parameters, which are the four base wavelengths (two for Y-direction, two for X-direction). Randomness and perspective adjustment are introduced at the beginning of each wave cycle.
Problem 3 - Sampling the Reflection:
With the water surface defined, the second part of the puzzle was to specify how to infer the reflection distortion using the wave map. In other words, how to retrace the light rays bouncing off the "uneven mirror".
Process: I relied on some basic rules of optics. When the program paints a pixel on the reflection canvass, it checks a corresponding surface point on the wave map. That surface point tells the program where to sample the color from the original image. When the wave map point is above the wave equilibrium, the reflection should be sampled with a downward offset. When the point is below the equilibrium, the reflection should be sampled with an upward offset.
Solution: The sampling offset correlates positively with the surface point's displacement from equilibrium, but negatively with the surface point's distance from the viewer. The displacement factor is linear, the distance factor non-linear. But for simplicity, I used linear expressions for both.
Further artistic enhancement were incorporated, which reduce the sample brightness as sampling point moves upwards on the original image, and as the reflection point moves closer to the viewer. Finally, the reflection image is blurred.
I also experimented with adjusting the brightness based on surface elevation, as well as shifting the offset based on the angle of the reflective surface at a given point. These factors are physically relevant, but their accuracy is fundamentally limited by the imperfection of the surface function. Consequently, they are not incorporated.
The final function takes an image and surface disturbance intensity as parameters, produces a reflection effect that is different each time. An artist can quickly generates multiple results and pick whichever seems best. Advanced parameters, which affect factors such as base wave frequencies, sampling offset, or brightness, can also be tweaked individually if desired.