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 }
复制代码
最终的结果是相当令人满意的一系列的涟漪初始扰动传播一个很好的流畅的动画效果可以安装和运行模拟点击这里请注意您可以使用鼠标滚轮放大/缩小
posted @   deepwzh  阅读(4519)  评论(1编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示