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 }
最终的结果是相当令人满意的一系列的涟漪初始扰动传播一个很好的流畅的动画效果可以安装和运行模拟点击这里请注意您可以使用鼠标滚轮放大/缩小

你可能感兴趣的:(WPF)