When you see the animation on the left (if it does not appear, please wait for a moment), you may not believe that it is actually made on a computer, which is the charm of the special effect "Water Wave.
Before introducing programming, let's review the knowledge about water waves we have learned in the physics class in high school.
Water Waves have the following features:
- Diffusion: When you drop a rock into the water, you will see a circle of water formed by the stone into the water point. Here, you may be misled by this phenomenon, it is wrong to think that each point on the water wave spreads outward at the center of the stone water point. In fact, any point on a water wave spreads around at any time with itself as the center of the circle. The reason is that a circular water wave is formed, the reason is that the water waves are offset by the symmetry of the diffusion.
- Attenuation: Because water has damping, otherwise, when you put stones into the pool, water waves will never stop fluctuating.
- Water Refraction: Because the skew angles of different places on the water waves are different, because of the water refraction, we can see from the vertical down of the observation point that the bottom of the water is not at the bottom of the observation point, but there is a certain offset. If we do not consider the light reflection on the top of the water surface, this is why we can feel the shape of water waves.
- Reflection: Water waves are reflected when they encounter obstacles.
- Diffraction: I suddenly thought of this, but I couldn't see it in the program. If I could put a reef in the center of the pool, or put a middle slit partition, then we can see the Diffraction Phenomenon of water waves.
Well, with these features, we can use mathematical and geometric knowledge to simulate real water waves. However, if you used 3dmax for water wave animation, you would know that it would take dozens of seconds to render a real-shape water wave image, what we need now is real-time rendering, which requires at least 20 frames per second for smooth display of water waves. Considering the speed of computer operations, it is impossible for us to construct water waves based on sine functions or precise formulas. We cannot use multiplication or division, nor use sin or cos. We can only use a fast algorithm to take an approximate value, although this algorithm has some errors, we have to do so to meet the requirements of real-time animation.
First, we need to create two arrays of the same size as the pool image.Buf1 [poolwidth * poolheight]AndBuf2 [poolwidth * poolheight](Poolwidth = pixel width of the Pool Image and poolheight = pixel height of the Pool Image), used to store the fluctuation data of the previous and next moments of each vertex on the water surface, because the amplitude also represents the wave energy, we will call these two arrays as the wave energy buffer. The water surface is a plane in the initial state, and the amplitude of each point is 0. Therefore, the initial values of these two arrays are equal to 0.
The formula for calculating the amplitude is derived below.
Assume that such a formula exists, you can calculate the amplitude of a point at any time based on the four points around a point, namely, the front, the back, the left, and the right, and the amplitude of the point itself, we may use induction to find the amplitude of any point on the surface at any time. As shown in the left figure, the amplitude of x0 is not affected by the amplitude of x0, at the same time, it is affected by four points (x1, x2, X3, X4) from the front, back, left, and right around it (to simplify it, we ignore all other points, the influence of these four points on A0 can be said to be equal opportunities. We can assume that the formula for this time is:
X0' = a (X1 + X2 + X3 + X4) + bx0 (Formula 1)
A and B are undetermined coefficients, and x0' is the amplitude of the next moment at 0.
X0, x1, x2, X3, and X4 are the amplitude of the current time.
Next we will solve a and B.
Assume that the damping of water is 0. Under this ideal condition, the total potential energy of water remains unchanged. That is to say, the sum of the amplitude of all vertices remains unchanged at any time. The following formula is obtained:
X0 '+ x1' +... + Xn' = x0 + X1 +... + Xn
Calculate each vertex as in Formula 1, and then substitute it into the formula to obtain:
(4A + B) x0 + (4A + B) X1 +... (4A + B) xn = x0 + X1 +... + Xn
=> 4A + B = 1
Find the simplest solution: a = 1/2, B =-1
Because 1/2 can be performed using the shift operator ">" without multiplication or division, this group of solutions is the most suitable and fastest. The final formula is:
X0' = (X1 + X2 + X3 + X4)/2-x0
Well, with the above approximate formula, you can extend it to the following general conclusion: if we know the volatility of any point on the water at a given moment, then, at the next moment, the amplitude of any point is equal to the amplitude of the four points before, after, left, and right next to the point divided by 2, and then minus the amplitude of the point.
It should be noted that there is damping in the water. Otherwise, using the above formula, once you add a source in the water, the water will never stop fluctuating. Therefore, we also need to attenuation the amplitude data so that after each point is calculated, the amplitude is reduced by a certain proportion than the ideal value. This attenuation rate has been tested. It is suitable to use 1/32, that is, 1/2 ^ 5. It can be quickly obtained through the shift operation.
By now, the most difficult part of the special effects of water waves has been used up. below is the code for calculating the amplitude data in the source program.
//************************************** *****************
// Calculate the bandwidth data buffer
//************************************** *****************
Void ripplespread ()
{
For (INT I = backwidth; I <backwidth * BACKHEIGHT-BACKWIDTH; I ++)
{
// Wave energy diffusion
Buf2 [I] = (buf1 [I-1] +
Buf1 [I + 1] +
Buf1 [I-backwidth] +
Buf1 [I + backwidth])
> 1)
-Buf2 [I];
// Wave Attenuation
Buf2 [I]-= buf2 [I]> 5;
}
// Exchange the Boundary Data Buffer
Short * pTMP = buf1;
Buf1 = buf2;
Buf2 = pTMP;
}
Now, we will render the page based on the calculated amplitude data.
Because of the refraction of water, when the water surface is not perpendicular to our line of sight, the underwater scenery we see is not at the bottom of the observation point, but there is a certain offset. The degree of offset is related to the slope of water waves, the refractive index of water, and the depth of water. If accurate calculations are required, it is obviously unrealistic. Similarly, we only need to perform linear approximation. Because the water surface is tilted, the offset of the Underwater scene is larger, we can approximate the amplitude difference between the front, back, and left of a point on the water surface to represent the offset of the Underwater scene.
In the program, the original image is loaded with one page and rendered with another page. Use the lock function to lock two pages, obtain the pointer pointing to the page memory area, and then copy each pixel in the original image to the rendering Page Based on the offset. The code for page rendering is as follows: (the code below is not optimized for ease of understanding. In fact, the optimized code is much more troublesome than it)
//************************************** *****************
// Render the off-screen Page Based on the wave energy data buffer
//************************************** *****************
Void renderripple ()
{
// Lock two off-screen pages
Ddsurfacedesc ddsd1, ddsd2;
Ddsd1.dwsize = sizeof (ddsurfacedesc );
Ddsd2.dwsize = sizeof (ddsurfacedesc );
Lpddspic1-> lock (null, & ddsd1, ddlock_wait, null );
Lpddspic2-> lock (null, & ddsd2, ddlock_wait, null );
// Get the page pixel depth and page memory pointer
Int depth = ddsd1.ddpfpixelformat. dwrgbbitcount/8;
Byte * bitmap1 = (byte *) ddsd1.lpsurface;
Byte * bitmap2 = (byte *) ddsd2.lpsurface;
// Perform page rendering below
Int xoff, yoff;
Int K = backwidth;
For (INT I = 1; I <BACKHEIGHT-1; I ++)
{
For (Int J = 0; j <backwidth; j ++)
{
// Calculate the offset
Xoff = buf1 [k-1]-buf1 [k + 1];
Yoff = buf1 [k-backwidth]-buf1 [K + backwidth];
// Determine whether the coordinates are within the window range
If (I + yoff) <0) {k ++; continue ;}
If (I + yoff)> backheight) {k ++; continue ;}
If (J + xoff) <0) {k ++; continue ;}
If (J + xoff)> backwidth) {k ++; continue ;}
// Calculate the memory address offset of the Offset pixel and the original Pixel
Int pos1, pos2;
Pos1 = ddsd1.lpitch * (I + yoff) + depth * (J + xoff );
Pos2 = ddsd2.lpitch * I + depth * J;
// Copy pixels
For (int d = 0; D <depth; D ++)
Bitmap2 [pos2 ++] = bitmap1 [pos1 ++];
K ++;
}
}
// Unlock the page
Lpddspic1-> unlock (& ddsd1 );
Lpddspic2-> unlock (& ddsd2 );
}
Add a source
As the saying goes: in order to form a water wave, we must add a source to the pool. You can imagine putting a stone into the water, the size and energy of the source are related to the radius of the stone and the strength of the stone you throw. Knowing this, we only need to modify the wave energy data buffer Buf so that it can bring a negative "sharp pulse" at the place where the stone enters the water, that is, let the Buf [x, y] =-n. After the experiment, the range of N is (32 ~ 128.
To control the source RADIUS, you only need to draw a circle with the radius of the stone as the center point of the stone entering the water, let all vertices in this circle come with such a negative "sharp pulse" (here we have done an approximate processing ).
The code for adding a source code is as follows:
//************************************** ***************
// Add a source.
//************************************** ***************
Void dropstone (int x, // X coordinate
Int y, // y coordinate
Int stonesize, // wave source RADIUS
Int stoneweight) // wave source energy
{
// Determine whether the coordinates are within the screen range
If (x + stonesize)> backwidth |
(Y + stonesize)> backheight |
(X-stonesize) <0 |
(Y-stonesize) <0)
Return;
For (INT posx = x-stonesize; posx <X + stonesize; posx ++)
For (INT posy = Y-stonesize; posy <Y + stonesize; posy ++)
If (posx-x) * (posx-x) + (posy-y) * (posy-y) <stonesize * stonesize)
Buf1 [backwidth * posy + posx] =-stoneweight;
}
So far, the production principles of water wave effects have all been revealed. In the above derivation, each step has undergone many seemingly excessively approximate processing, but you don't have to worry about it. facts have proved that using this method, in terms of speed and image, we can achieve very good results. There are very detailed comments in the source program. It is not a problem to read them carefully.
This program is DirectX programming under Win32 and does not use any packaging library. On my computer (AMDK6-200, 2 mvram, 64 msram), the image size of 320x240 can reach 25 frames per second. Unlike the previous programs, this program uses the window mode, so debugging is very convenient. If you are not familiar with window Mode Programming, this program is also a good example.
The biggest advantage of this method of water wave processing for images using data buffers is that the speed of program operations and its display is irrelevant to the complexity of water waves, no matter whether the water is calm or turbulent, the FPs of the program remains unchanged. You can check out the program after studying it. In fact, if you have mastered this method, you can promote it to make other special effects, such as smoke, atmosphere, and sunlight, I am also working on making these special effects. I believe there will be new gains soon.