📄 水波的特效.htm
字号:
<TABLE align=center border=0 cellPadding=0 cellSpacing=0
class=BGWhite width="100%">
<TBODY>
<TR>
<TD>
<DIV align=center><SPAN class=ColorCode>(4a+b)X0 + (4a+b)X1 +
... (4a+b)Xn = X0 + X1 + ... + Xn => 4a + b =
1</SPAN></DIV></TD></TR></TBODY></TABLE><BR> 找出一个最简解:a = 1/2、b =
-1。因为1/2可以用移位运算符“>>”来进行,不用进行乘除法,所以,这组解是最适用的而且是最快的。那么最后得到的公式就是:<BR><BR>
<TABLE align=center border=0 cellPadding=0 cellSpacing=0
class=BGWhite width="100%">
<TBODY>
<TR>
<TD>
<DIV align=center><SPAN class=ColorCode>X0' = (X1+X2+X3+X4) /
2 -
X0</SPAN></DIV></TD></TR></TBODY></TABLE><BR> 好了,有了上面这个近似公式,你就可以推广到下面这个一般结论:已知某一时刻水面上任意一点的波幅,那么,在下一时刻,任意一点的波幅就等于与该点紧邻的前、后、左、右四点的波幅的和除以2、再减去该点的波幅。<BR> 应该注意到,水在实际中是存在阻尼的,否则,用上面这个公式,一旦你在水中增加一个波源,水面将永不停止的震荡下去。所以,还需要对波幅数据进行衰减处理,让每一个点在经过一次计算后,波幅都比理想值按一定的比例降低。这个衰减率经过测试,用1/32比较合适,也就是1/2^5。可以通过移位运算很快的获得。<BR> 到这里,水波特效制作中最艰难的部分已经度过了,下面是源程序中计算波幅数据的代码。<BR><BR> <SPAN
class=ColorCatchword>//*******************************************************<BR> //
计算波能数据缓冲区<BR> //*******************************************************<BR></SPAN> v<SPAN
class=ColorCode>oid RippleSpread()<BR> {<BR> for (int
i=BACKWIDTH; i<BACKWIDTH*BACKHEIGHT-BACKWIDTH;
i++)<BR> {<BR></SPAN> <SPAN class=ColorCatchword>//
波能扩散<BR></SPAN> <SPAN class=ColorCode>buf2[i] =
((buf1[i-1]+<BR> buf1[i+1]+<BR> buf1[i-BACKWIDTH]+<BR> buf1[i+BACKWIDTH])>>1)-<BR> buf2[i];<BR></SPAN> <SPAN
class=ColorCatchword>// 波能衰减<BR></SPAN> <SPAN
class=ColorCode>buf2[i] -=
buf2[i]>>5;<BR> }<BR></SPAN><BR> <SPAN
class=ColorCatchword>// 交换波能数据缓冲区<BR></SPAN> <SPAN
class=ColorCode>short *ptmp =buf1;<BR> buf1 = buf2;<BR> buf2 =
ptmp;<BR> }<BR></SPAN><A
name=3.0></A><BR><B>3、进行渲染</B><BR> 下面再来根据算出的波幅数据对页面进行渲染。因为水的折射,当水面不与我们的视线相垂直的时候,我们所看到的水下的景物并不是在观察点的正下方,而存在一定的偏移。偏移的程度与水波的斜率,水的折射率和水的深度都有关系,如果要进行精确的计算的话,显然是很不现实的。同样,我们只需要做线形的近似处理就行了。因为水面越倾斜,所看到的水下景物偏移量就越大,所以,我们可以近似的用水面上某点的前后、左右两点的波幅之差来代表所看到水底景物的偏移量。<BR> 在程序中,用一个页面装载原始的图象,用另外一个页面来进行渲染。先用Lock函数锁定两个页面,取得指向页面内存区的指针,然后用根据偏移量将原始图象上的每一个象素复制到渲染页面上。进行页面渲染的代码如下(下面的代码为了便于理解,并没有进行优化,实际上,优化后的代码比它要麻烦许多):<BR><BR> <SPAN
class=ColorCatchword>//*******************************************************<BR> //
根据波能数据缓冲区对离屏页面进行渲染<BR> //*******************************************************<BR></SPAN> <SPAN
class=ColorCode>void RenderRipple()<BR> {<BR></SPAN> <SPAN
class=ColorCatchword>// 锁定两个离屏页面<BR></SPAN> <SPAN
class=ColorCode>DDSURFACEDESC ddsd1, ddsd2;<BR> ddsd1.dwSize =
sizeof(DDSURFACEDESC);<BR> ddsd2.dwSize =
sizeof(DDSURFACEDESC);<BR> lpDDSPic1->Lock(NULL, &ddsd1,
DDLOCK_WAIT, NULL);<BR> lpDDSPic2->Lock(NULL, &ddsd2,
DDLOCK_WAIT, NULL);<BR></SPAN><BR> <SPAN class=ColorCatchword>//
取得页面象素位深度,和页面内存指针<BR></SPAN> <SPAN class=ColorCode>int
depth=ddsd1.ddpfPixelFormat.dwRGBBitCount/8;<BR> BYTE *Bitmap1 =
(BYTE*)ddsd1.lpSurface;<BR> BYTE *Bitmap2 =
(BYTE*)ddsd2.lpSurface;<BR></SPAN><BR> <SPAN
class=ColorCatchword>// 下面进行页面渲染<BR></SPAN> <SPAN
class=ColorCode>int xoff, yoff;<BR> int k = BACKWIDTH;<BR> for
(int i=1; i<BACKHEIGHT-1; i++)<BR> {<BR> for (int j=0;
j>BACKWIDTH; j++)<BR> {<BR></SPAN> <SPAN
class=ColorCatchword>// 计算偏移量<BR></SPAN> <SPAN
class=ColorCode>xoff = buf1[k-1]-buf1[k+1];<BR> yoff =
buf1[k-BACKWIDTH]-buf1[k+BACKWIDTH];<BR></SPAN><BR> <SPAN
class=ColorCatchword>// 判断坐标是否在窗口范围内<BR></SPAN> <SPAN
class=ColorCode>if ((i+yoff )< 0 ) { k++; continue;
}<BR> if ((i+yoff )> BACKHEIGHT) { k++; continue;
}<BR> if ((j+xoff )< 0 ) { k++; continue; }<BR> if
((j+xoff )> BACKWIDTH ) { k++; continue;
}<BR></SPAN><BR> <SPAN class=ColorCatchword>//
计算出偏移象素和原始象素的内存地址偏移量<BR></SPAN> <SPAN class=ColorCode>int
pos1, pos2;<BR> pos1=ddsd1.lPitch*(i+yoff)+
depth*(j+xoff);<BR> pos2=ddsd2.lPitch*i+
depth*j;<BR></SPAN><BR> <SPAN class=ColorCatchword>//
复制象素<BR></SPAN> <SPAN class=ColorCode>for (int d=0; d <
depth;
d++)<BR> Bitmap2[pos2++]=Bitmap1[pos1++];<BR> k++;<BR> }<BR> }<BR></SPAN> <SPAN
class=ColorCatchword>// 解锁页面<BR></SPAN> <SPAN
class=ColorCode>lpDDSPic1->Unlock(&ddsd1);<BR> lpDDSPic2->Unlock(&ddsd2);<BR> }</SPAN><BR><A
name=4.0></A><BR><B>4、增加波源</B><BR> 为了形成水波,我们必须在水池中加入波源,你可以想象成向水中投入石头,形成的波源的大小和能量与石头的半径和你扔石头的力量都有关系。知道了这些,那么好,我们只要修改波能数据缓冲区buf,让它在石头入水的地点来一个负的“尖脉冲”,即让buf[x,y]=-n。经过实验,n的范围在(32~128)之间比较合适。<BR> 控制波源半径也好办,你只要以石头入水中心点为圆心,画一个以石头半径为半径的圆,让这个圆中所有的点都来这么一个负的“尖脉冲”就可以了(这里也做了近似处理)。增加波源的代码如下:<BR><BR> <SPAN
class=ColorCatchword>//*****************************************************<BR> //
增加波源<BR> //*****************************************************<BR></SPAN> <SPAN
class=ColorCode>void DropStone(int x,</SPAN> <SPAN
class=ColorCatchword>// x坐标<BR></SPAN> <SPAN
class=ColorCode>int y,</SPAN> <SPAN class=ColorCatchword>//
y坐标<BR></SPAN> <SPAN class=ColorCode>int stonesize,</SPAN>
<SPAN class=ColorCatchword>// 波源半径<BR></SPAN> <SPAN
class=ColorCode>int stoneweight )</SPAN> <SPAN
class=ColorCatchword>// 波源能量<BR></SPAN> <SPAN
class=ColorCode>{<BR></SPAN> <SPAN class=ColorCatchword>//
判断坐标是否在屏幕范围内<BR></SPAN> <SPAN
class=ColorCode>if((x+stonesize)>BACKWIDTH ||<BR>
(y+stonesize)>BACKHEIGHT||<BR> (x-stonesize)<0 ||
(y-stonesize)<0) return;<BR><BR> for (int posx=x-stonesize;
posx<x+stonesize; posx++)<BR> for (int posy=y-stonesize;
posy>y+stonesize; posy++)<BR> if((posx-x)*(posx-x) +
(posy-y)*(posy-y) <
stonesize*stonesize)<BR> buf1[BACKWIDTH*posy+posx] =
-stoneweight;<BR> }<BR></SPAN><BR> 至此,水波特效的制作原理就此就全部揭示了。在上面的推导中,每一步都进行了很多看似非常过分的近似处理,但是,你完全不必担心,事实证明,用这种方法,在速度和图象上都可以获得非常好的效果。源程序中有非常详尽的注释,仔细推敲一下,看懂它们应该不成问题。<BR>
这个程序是Windows下的DirectX编程,没有使用任何包装库。在我的电脑上(<SPAN class=English>AMD
K6-200、2MBVRam、64MBSDRam</SPAN>),320×240的画面大小,每秒可以达到25帧。与前几个程序不一样,这个程序使用了窗口模式,所以调试起来很方便。如果你对窗口模式编程不熟悉,这个程序也是一个很好的例子。<BR> 这种用数据缓冲区对图象进行水波处理的方法,有个最大的好处就是,程序运算和其示的速度与水波的复杂程度是没有关系的,无论水面是风平浪静还是波涛汹涌,程序的FPS始终保持不变,这一点你研究一下程序就应该可以看出来。实际上,如果你掌握了这种方法,将这种方法推广一下,完全可以做出另外一些特殊的效果,如烟雾、大气、阳光等,我现在也正在研究这些特效的制作,相信不久以后就会有新的收获。<BR><BR> <IMG
height=16 src="水波的特效.files/download.gif" width=16> <A
class=BlackLink
href="http://imagic3d.com/ripsample.zip">演示程序及源代码</A>(<SPAN
class=English>LINK</SPAN>:<A class=BlackLink
href="http://imagic3d.com/" target=_blank>Imagic工作室</A>) <!-- #EndEditable --></TD></TR>
<TR>
<TD class=BGGray width="100%">
<DIV align=right><!-- #BeginEditable "%B2%D9%D7%F7%CC%A8" --><A
class=BlackLink
href="http://mays.soage.com/develop/effect/200111/dx_Ripple.htm#7758258"><B>返回</B></A><!-- #EndEditable -->
</DIV></TD></TR>
<TR>
<TD class=BGGray width="100%"><!-- #BeginEditable "%C4%DA%C8%DD%B0%E6%C8%A8" -->
<SCRIPT language=JavaScript src="水波的特效.files/effect.CR.js"></SCRIPT>
<!-- #EndEditable --></TD></TR></TBODY></TABLE></TD></TR>
<TR>
<TD width=0%></TD>
<TD class=BGGray colSpan=2 height=3 vAlign=top></TD>
<TD colSpan=2></TD>
<TD colSpan=3 vAlign=top width="84%"></TD></TR></TBODY></TABLE><BR><!-- #BeginEditable "%B0%E6%C8%A8" -->
<SCRIPT language=JavaScript src="水波的特效.files/copyright.4.js"></SCRIPT>
<SCRIPT language=JavaScript src="水波的特效.files/scroll.js"></SCRIPT>
<SCRIPT language=JavaScript src="水波的特效.files/return.main.js"></SCRIPT>
<SCRIPT language=JavaScript src="水波的特效.files/return.sub.js"></SCRIPT>
<!-- #EndEditable --><!-- #EndTemplate --></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -