wpf 3d水波效果(翻译文)

 

      很多年前(80年代中期),我在一个公司,硅谷图形工作站的工作。其中少数炫耀的SGI机器的高端图形演示是在一个小线框网格模拟波的传播。这是很好玩通过改变网格点的高度和然后又让模拟运行。和SGI的机器不够快,由此产生的动画只是如痴如醉。

      在WPF再造水面模拟似乎是一个很好的方式,多一点了解WPF中的三维图形。 最终的结果是在这里)。

     第一步是要找到一种算法,通过水模拟波传播。原来,是一个非常简单的算法,简单地以邻近点的平均身高达到预期的效果。在这篇文章中描述的基本算法详细的二维水面。相同的算法,也描述了在水面解释的影响。

    下一步是成立的3D视图,其构成要素。我用两个不同的方向灯,以创造更多的水面,以及确定水面漫反射和镜面材料的性能对比。

   以下是有关XAML。请注意,meshMain网将包含水面。

 

 1 <Viewport3D Name="viewport3D1" Margin="0,8.181,0,0" Grid.Row="1">
 2     <Viewport3D.Camera>
 3         <PerspectiveCamera x:Name="camMain" Position="48 7.8 41" LookDirection="-48 -7.8 -41" FarPlaneDistance="100" UpDirection="0,1,0" NearPlaneDistance="1" FieldOfView="70">
 4 
 5         </PerspectiveCamera>
 6     </Viewport3D.Camera>
 7     <ModelVisual3D x:Name="vis3DLighting">
 8         <ModelVisual3D.Content>
 9             <DirectionalLight x:Name="dirLightMain" Direction="2, -2, 0"/>
10         </ModelVisual3D.Content>
11     </ModelVisual3D>
12     <ModelVisual3D>
13         <ModelVisual3D.Content>
14             <DirectionalLight Direction="0, -2, 2"/>
15         </ModelVisual3D.Content>
16     </ModelVisual3D>
17     <ModelVisual3D>
18         <ModelVisual3D.Content>
19             <GeometryModel3D x:Name="gmodMain">
20                 <GeometryModel3D.Geometry>
21                     <MeshGeometry3D x:Name="meshMain" >
22                     </MeshGeometry3D>
23                 </GeometryModel3D.Geometry>
24                 <GeometryModel3D.Material>
25                     <MaterialGroup>
26                         <DiffuseMaterial x:Name="matDiffuseMain">
27                             <DiffuseMaterial.Brush>
28                                 <SolidColorBrush Color="DarkBlue"/>
29                             </DiffuseMaterial.Brush>
30                         </DiffuseMaterial>
31                         <SpecularMaterial SpecularPower="24">
32                             <SpecularMaterial.Brush>
33                                 <SolidColorBrush Color="LightBlue"/>
34                             </SpecularMaterial.Brush>
35                         </SpecularMaterial>
36                     </MaterialGroup>
37                 </GeometryModel3D.Material>
38             </GeometryModel3D>
39         </ModelVisual3D.Content>
40     </ModelVisual3D>
41 </Viewport3D>
     下一步,我们创建了一个WaveGrid类,实现以上所述的基本算法。其基本思路是,我们保持两个单独的缓冲区网的数据,表示当前状态的水和一个以前的状态。 WaveGrid这个数据存储两个Point3DCollection对象。当我们运行仿真,我们轮流缓冲区,我们正在编写和我们MeshGeometry3D.Positions属性附加到最近的缓冲区。请注意,我们只有改变点,垂直高度为Y值。

WaveGrid还建立了三角形网格指数,这也将连接到我们的MeshGeometry3D Int32Collection

    所有有趣的东西在ProcessWater发生。这是我们实现在文章中描述的平滑算法。因为我想充分动画网格中的每一点,我处理不只是内部点有四个相邻点,但沿网格的边缘点,以及。正如我们在邻近点的高度值,我们保持多少邻居,我们发现,使我们能够做的平均正确的轨道。

每个点的最终值是平滑(你的邻居的平均身高)和速度,这是基本上如何远离平衡点,在最后一次迭代的功能?我们也应用阻尼系数,因为波将逐渐失去它们的振幅。

这里的为WaveGrid的源代码
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Windows.Media;
  6 using System.Windows.Media.Media3D;
  7 
  8 namespace WaveSim
  9 {
 10     class WaveGrid
 11     {
 12         // Constants
 13         const int MinDimension = 5;    
 14         const double Damping = 0.96;
 15         const double SmoothingFactor = 2.0;     // Gives more weight to smoothing than to velocity
 16 
 17         // Private member data
 18         private Point3DCollection _ptBuffer1;
 19         private Point3DCollection _ptBuffer2;
 20         private Int32Collection _triangleIndices;
 21 
 22         private int _dimension;
 23 
 24         // Pointers to which buffers contain:
 25         //    - Current: Most recent data
 26         //    - Old: Earlier data
 27         // These two pointers will swap, pointing to ptBuffer1/ptBuffer2 as we cycle the buffers
 28         private Point3DCollection _currBuffer;
 29         private Point3DCollection _oldBuffer;
 30 
 31         /// <summary>
 32         /// Construct new grid of a given dimension
 33         /// </summary>
 34         ///
 35 <param name="Dimension"></param>
 36         public WaveGrid(int Dimension)
 37         {
 38             if (Dimension < MinDimension)
 39                 throw new ApplicationException(string.Format("Dimension must be at least {0}", MinDimension.ToString()));
 40 
 41             _ptBuffer1 = new Point3DCollection(Dimension * Dimension);
 42             _ptBuffer2 = new Point3DCollection(Dimension * Dimension);
 43             _triangleIndices = new Int32Collection((Dimension - 1) * (Dimension - 1) * 2);
 44 
 45             _dimension = Dimension;
 46 
 47             InitializePointsAndTriangles();
 48 
 49             _currBuffer = _ptBuffer2;
 50             _oldBuffer = _ptBuffer1;
 51         }
 52 
 53         /// <summary>
 54         /// Access to underlying grid data
 55         /// </summary>
 56         public Point3DCollection Points
 57         {
 58             get { return _currBuffer; }
 59         }
 60 
 61         /// <summary>
 62         /// Access to underlying triangle index collection
 63         /// </summary>
 64         public Int32Collection TriangleIndices
 65         {
 66             get { return _triangleIndices; }
 67         }
 68 
 69         /// <summary>
 70         /// Dimension of grid--same dimension for both X & Y
 71         /// </summary>
 72         public int Dimension
 73         {
 74             get { return _dimension; }
 75         }
 76 
 77         /// <summary>
 78         /// Set center of grid to some peak value (high point).  Leave
 79         /// rest of grid alone.  Note: If dimension is even, we're not
 80         /// exactly at the center of the grid--no biggie.
 81         /// </summary>
 82         ///
 83         <param name="PeakValue"></param>
 84         public void SetCenterPeak(double PeakValue)
 85         {
 86             int nCenter = (int)_dimension / 2;
 87 
 88             // Change data in oldest buffer, then make newest buffer
 89             // become oldest by swapping
 90             Point3D pt = _oldBuffer[(nCenter * _dimension) + nCenter];
 91             pt.Y = (int)PeakValue;
 92             _oldBuffer[(nCenter * _dimension) + nCenter] = pt;
 93 
 94             SwapBuffers();
 95         }
 96 
 97         /// <summary>
 98         /// Leave buffers in place, but change notation of which one is most recent
 99         /// </summary>
100         private void SwapBuffers()
101         {
102             Point3DCollection temp = _currBuffer;
103             _currBuffer = _oldBuffer;
104             _oldBuffer = temp;
105         }
106 
107         /// <summary>
108         /// Clear out points/triangles and regenerates
109         /// </summary>
110         ///
111         <param name="grid"></param>
112         private void InitializePointsAndTriangles()
113         {
114             _ptBuffer1.Clear();
115             _ptBuffer2.Clear();
116             _triangleIndices.Clear();
117 
118             int nCurrIndex = 0;     // March through 1-D arrays
119 
120             for (int row = 0; row < _dimension; row++)
121             {
122                 for (int col = 0; col < _dimension; col++)
123                 {
124                     // In grid, X/Y values are just row/col numbers
125                     _ptBuffer1.Add(new Point3D(col, 0.0, row));
126 
127                     // Completing new square, add 2 triangles
128                     if ((row > 0) && (col > 0))
129                     {
130                         // Triangle 1
131                         _triangleIndices.Add(nCurrIndex - _dimension - 1);
132                         _triangleIndices.Add(nCurrIndex);
133                         _triangleIndices.Add(nCurrIndex - _dimension);
134 
135                         // Triangle 2
136                         _triangleIndices.Add(nCurrIndex - _dimension - 1);
137                         _triangleIndices.Add(nCurrIndex - 1);
138                         _triangleIndices.Add(nCurrIndex);
139                     }
140 
141                     nCurrIndex++;
142                 }
143             }
144 
145             // 2nd buffer exists only to have 2nd set of Z values
146             _ptBuffer2 = _ptBuffer1.Clone();
147         }
148 
149         /// <summary>
150         /// Determine next state of entire grid, based on previous two states.
151         /// This will have the effect of propagating ripples outward.
152         /// </summary>
153         public void ProcessWater()
154         {
155             // Note that we write into old buffer, which will then become our
156             //    "current" buffer, and current will become old. 
157             // I.e. What starts out in _currBuffer shifts into _oldBuffer and we
158             // write new data into _currBuffer.  But because we just swap pointers,
159             // we don't have to actually move data around.
160 
161             // When calculating data, we don't generate data for the cells around
162             // the edge of the grid, because data smoothing looks at all adjacent
163             // cells.  So instead of running [0,n-1], we run [1,n-2].
164 
165             double velocity;    // Rate of change from old to current
166             double smoothed;    // Smoothed by adjacent cells
167             double newHeight;
168             int neighbors;
169 
170             int nPtIndex = 0;   // Index that marches through 1-D point array
171 
172             // Remember that Y value is the height (the value that we're animating)
173             for (int row = 0; row < _dimension ; row++)
174             {
175                 for (int col = 0; col < _dimension; col++)
176                 {
177                     velocity = -1.0 * _oldBuffer[nPtIndex].Y;     // row, col
178                     smoothed = 0.0;
179 
180                     neighbors = 0;
181                     if (row > 0)    // row-1, col
182                     {
183                         smoothed += _currBuffer[nPtIndex - _dimension].Y;
184                         neighbors++;
185                     }
186 
187                     if (row < (_dimension - 1))   // row+1, col
188                     {
189                         smoothed += _currBuffer[nPtIndex + _dimension].Y;
190                         neighbors++;
191                     }
192 
193                     if (col > 0)          // row, col-1
194                     {
195                         smoothed += _currBuffer[nPtIndex - 1].Y;
196                         neighbors++;
197                     }
198 
199                     if (col < (_dimension - 1))   // row, col+1
200                     {
201                         smoothed += _currBuffer[nPtIndex + 1].Y;
202                         neighbors++;
203                     }
204 
205                     // Will always have at least 2 neighbors
206                     smoothed /= (double)neighbors;
207 
208                     // New height is combination of smoothing and velocity
209                     newHeight = smoothed * SmoothingFactor + velocity;
210 
211                     // Damping
212                     newHeight = newHeight * Damping;
213 
214                     // We write new data to old buffer
215                     Point3D pt = _oldBuffer[nPtIndex];
216                     pt.Y = newHeight;   // row, col
217                     _oldBuffer[nPtIndex] = pt;
218 
219                     nPtIndex++;
220                 }
221             }
222 
223             SwapBuffers();
224         }
225     }
226 }
最后,我们需要挂钩一切。当我们的主窗口触发,我们创建一个WaveGrid实例和一些峰值设定在网格的中心点。当我们开始动画,这个高点回落,引发的海浪。

我们在做CompositionTarget.Rendering事件处理程序的所有动画。这是推荐的当场做WPF中的自定义动画,而不是做一些计时器的Tick事件中的动画。 Windows Presentation Foundation中如虎添翼,弥敦道,470页)。

当你渲染事件的处理程序,WPF只是继续无限期地渲染帧。一个问题是,该处理程序将调用每一个渲染帧,原来是太快为我们的水动画。为了获得水权看,我们保持的时间,我们最后呈现了一个框架,然后等待指定的毫秒数,前呈现另一个轨道。

在这里是Window1.xaml.cs完整的源代码:
 
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Diagnostics;
  4 using System.Linq;
  5 using System.Text;
  6 using System.Windows;
  7 using System.Windows.Controls;
  8 using System.Windows.Data;
  9 using System.Windows.Documents;
 10 using System.Windows.Input;
 11 using System.Windows.Media;
 12 using System.Windows.Media.Media3D;
 13 using System.Windows.Media.Imaging;
 14 using System.Windows.Navigation;
 15 using System.Windows.Shapes;
 16 using System.Windows.Threading;
 17 
 18 namespace WaveSim
 19 {
 20     /// <summary>
 21     /// Interaction logic for Window1.xaml
 22     /// </summary>
 23     public partial class Window1 : Window
 24     {
 25         private Vector3D zoomDelta;
 26 
 27         private WaveGrid _grid;
 28         private bool _rendering;
 29         private double _lastTimeRendered;
 30         private double _firstPeak = 6.5;
 31 
 32         // Values to try:
 33         //   GridSize=20, RenderPeriod=125
 34         //   GridSize=50, RenderPeriod=50
 35         private const int GridSize = 50;   
 36         private const double RenderPeriodInMS = 50;    
 37 
 38         public Window1()
 39         {
 40             InitializeComponent();
 41 
 42             _grid = new WaveGrid(GridSize);        // 10x10 grid
 43             slidPeakHeight.Value = _firstPeak;
 44             _grid.SetCenterPeak(_firstPeak);
 45             meshMain.Positions = _grid.Points;
 46             meshMain.TriangleIndices = _grid.TriangleIndices;
 47 
 48             // On each WheelMouse change, we zoom in/out a particular % of the original distance
 49             const double ZoomPctEachWheelChange = 0.02;
 50             zoomDelta = Vector3D.Multiply(ZoomPctEachWheelChange, camMain.LookDirection);
 51         }
 52 
 53         private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
 54         {
 55             if (e.Delta > 0)
 56                 // Zoom in
 57                 camMain.Position = Point3D.Add(camMain.Position, zoomDelta);
 58             else
 59                 // Zoom out
 60                 camMain.Position = Point3D.Subtract(camMain.Position, zoomDelta);
 61             Trace.WriteLine(camMain.Position.ToString());
 62         }
 63 
 64         // Start/stop animation
 65         private void btnStart_Click(object sender, RoutedEventArgs e)
 66         {
 67             if (!_rendering)
 68             {
 69                 _grid = new WaveGrid(GridSize);        // 10x10 grid
 70                 _grid.SetCenterPeak(_firstPeak);
 71                 meshMain.Positions = _grid.Points;
 72 
 73                 _lastTimeRendered = 0.0;
 74                 CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
 75                 btnStart.Content = "Stop";
 76                 slidPeakHeight.IsEnabled = false;
 77                 _rendering = true;
 78             }
 79             else
 80             {
 81                 CompositionTarget.Rendering -= new EventHandler(CompositionTarget_Rendering);
 82                 btnStart.Content = "Start";
 83                 slidPeakHeight.IsEnabled = true;
 84                 _rendering = false;
 85             }
 86         }
 87 
 88         void CompositionTarget_Rendering(object sender, EventArgs e)
 89         {
 90             RenderingEventArgs rargs = (RenderingEventArgs)e;
 91             if ((rargs.RenderingTime.TotalMilliseconds - _lastTimeRendered) > RenderPeriodInMS)
 92             {
 93                 // Unhook Positions collection from our mesh, for performance
 94                 // (see http://blogs.msdn.com/timothyc/archive/2006/08/31/734308.aspx)
 95                 meshMain.Positions = null;
 96 
 97                 // Do the next iteration on the water grid, propagating waves
 98                 _grid.ProcessWater();
 99 
100                 // Then update our mesh to use new Z values
101                 meshMain.Positions = _grid.Points;
102 
103                 _lastTimeRendered = rargs.RenderingTime.TotalMilliseconds;
104             }
105         }
106 
107         private void slidPeakHeight_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
108         {
109             _firstPeak = slidPeakHeight.Value;
110             _grid.SetCenterPeak(_firstPeak);
111         }
112     }
113 }
最终的结果是相当令人满意的一系列的涟漪,从初始扰动传播的一个很好的流畅的动画效果。你可以安装和运行的模拟点击这里。请注意您可以使用鼠标滚轮放大/缩小。
最后,我们需要挂钩一切。当我们的主窗口触发,我们创建一个WaveGrid实例和一些峰值设定在网格的中心点。当我们开始动画,这个高点回落,引发的海浪。

我们在做CompositionTarget.Rendering事件处理程序的所有动画。这是推荐的当场做WPF中的自定义动画,而不是做一些计时器的Tick事件中的动画。 Windows Presentation Foundation中如虎添翼,弥敦道,470页)。

当你渲染事件的处理程序,WPF只是继续无限期地渲染帧。一个问题是,该处理程序将调用每一个渲染帧,原来是太快为我们的水动画。为了获得水权看,我们保持的时间,我们最后呈现了一个框架,然后等待指定的毫秒数,前呈现另一个轨道。

在这里是Window1.xaml.cs完整的源代码:
 
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Diagnostics;
  4 using System.Linq;
  5 using System.Text;
  6 using System.Windows;
  7 using System.Windows.Controls;
  8 using System.Windows.Data;
  9 using System.Windows.Documents;
 10 using System.Windows.Input;
 11 using System.Windows.Media;
 12 using System.Windows.Media.Media3D;
 13 using System.Windows.Media.Imaging;
 14 using System.Windows.Navigation;
 15 using System.Windows.Shapes;
 16 using System.Windows.Threading;
 17 
 18 namespace WaveSim
 19 {
 20     /// <summary>
 21     /// Interaction logic for Window1.xaml
 22     /// </summary>
 23     public partial class Window1 : Window
 24     {
 25         private Vector3D zoomDelta;
 26 
 27         private WaveGrid _grid;
 28         private bool _rendering;
 29         private double _lastTimeRendered;
 30         private double _firstPeak = 6.5;
 31 
 32         // Values to try:
 33         //   GridSize=20, RenderPeriod=125
 34         //   GridSize=50, RenderPeriod=50
 35         private const int GridSize = 50;   
 36         private const double RenderPeriodInMS = 50;    
 37 
 38         public Window1()
 39         {
 40             InitializeComponent();
 41 
 42             _grid = new WaveGrid(GridSize);        // 10x10 grid
 43             slidPeakHeight.Value = _firstPeak;
 44             _grid.SetCenterPeak(_firstPeak);
 45             meshMain.Positions = _grid.Points;
 46             meshMain.TriangleIndices = _grid.TriangleIndices;
 47 
 48             // On each WheelMouse change, we zoom in/out a particular % of the original distance
 49             const double ZoomPctEachWheelChange = 0.02;
 50             zoomDelta = Vector3D.Multiply(ZoomPctEachWheelChange, camMain.LookDirection);
 51         }
 52 
 53         private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
 54         {
 55             if (e.Delta > 0)
 56                 // Zoom in
 57                 camMain.Position = Point3D.Add(camMain.Position, zoomDelta);
 58             else
 59                 // Zoom out
 60                 camMain.Position = Point3D.Subtract(camMain.Position, zoomDelta);
 61             Trace.WriteLine(camMain.Position.ToString());
 62         }
 63 
 64         // Start/stop animation
 65         private void btnStart_Click(object sender, RoutedEventArgs e)
 66         {
 67             if (!_rendering)
 68             {
 69                 _grid = new WaveGrid(GridSize);        // 10x10 grid
 70                 _grid.SetCenterPeak(_firstPeak);
 71                 meshMain.Positions = _grid.Points;
 72 
 73                 _lastTimeRendered = 0.0;
 74                 CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
 75                 btnStart.Content = "Stop";
 76                 slidPeakHeight.IsEnabled = false;
 77                 _rendering = true;
 78             }
 79             else
 80             {
 81                 CompositionTarget.Rendering -= new EventHandler(CompositionTarget_Rendering);
 82                 btnStart.Content = "Start";
 83                 slidPeakHeight.IsEnabled = true;
 84                 _rendering = false;
 85             }
 86         }
 87 
 88         void CompositionTarget_Rendering(object sender, EventArgs e)
 89         {
 90             RenderingEventArgs rargs = (RenderingEventArgs)e;
 91             if ((rargs.RenderingTime.TotalMilliseconds - _lastTimeRendered) > RenderPeriodInMS)
 92             {
 93                 // Unhook Positions collection from our mesh, for performance
 94                 // (see http://blogs.msdn.com/timothyc/archive/2006/08/31/734308.aspx)
 95                 meshMain.Positions = null;
 96 
 97                 // Do the next iteration on the water grid, propagating waves
 98                 _grid.ProcessWater();
 99 
100                 // Then update our mesh to use new Z values
101                 meshMain.Positions = _grid.Points;
102 
103                 _lastTimeRendered = rargs.RenderingTime.TotalMilliseconds;
104             }
105         }
106 
107         private void slidPeakHeight_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
108         {
109             _firstPeak = slidPeakHeight.Value;
110             _grid.SetCenterPeak(_firstPeak);
111         }
112     }
113 }
最终的结果是相当令人满意的一系列的涟漪,从初始扰动传播的一个很好的流畅的动画效果。你可以安装和运行的模拟点击这里。请注意您可以使用鼠标滚轮放大/缩小。

转载于:https://www.cnblogs.com/Wade-/archive/2012/07/13/2589687.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值