WorldWind学习系列十:RendableObject中的DirectX渲染分析——ImageLayer为例

it2024-11-01  15

  学习WW有一段时间了,但是若想开发自己基于WW的插件,必然会遇到RendableObject中的DirectX渲染问题。所有需要渲染绘制的WW三维插件,最终是通过继承RendableObject并实现自己的Initialize()、Update()、Render()方法的。想写自己的Render()方法不是简单的事情,你必然要学习DirectX编程,否则,你连看懂示例中的底层Render()方法都很难,谈何开发自己的插件。

  为了突破DirectX编程对我学习WW插件的阻挠,我“快餐式”地突击学习了DirectX,对基本流程和基本原理有了一定认识。今天下午我也对BMNG插件中RendableObject子类——ImageLayer为例进行了分析学习。现在与大家分享一下。

注:请务必先学过Direct3D学习(资料收集),否则看下面的内容无异于浪费你宝贵的时间。

  Image Layer重载实现的Initialize()方法。

Initialize方法代码          ///   <summary>          ///  Layer initialization code         ///   </summary>          public   override   void  Initialize(DrawArgs drawArgs)        {             try             {         //获取绘制设备对象                 this .device  =  drawArgs.device;                 if (_imagePath  ==   null   &&  _imageUrl  !=   null   &&  _imageUrl.ToLower().StartsWith( " http:// " ))                {            //根据URL地址,在本地构建同样层次的文件夹路径。 这里有个知识点 getFilePathFromUrl()方法,自己定位学习                     _imagePath  =  getFilePathFromUrl(_imageUrl);                                    }                FileInfo imageFileInfo  =   null ;                 if (_imagePath  !=   null )                    imageFileInfo  =   new  FileInfo(_imagePath);                 if (downloadThread  !=   null   &&  downloadThread.IsAlive)                     return ;                 if (_imagePath  !=   null   &&                      cacheExpiration  !=  TimeSpan.MaxValue  &&                     cacheExpiration.TotalMilliseconds  >   0   &&                     _imageUrl.ToLower().StartsWith( " http:// " &&                     imageFileInfo  !=   null   &&                      imageFileInfo.Exists  &&                     imageFileInfo.LastWriteTime  <  System.DateTime.Now  -  cacheExpiration)                {                     // attempt to redownload it                   //启用新线程,下载影像图片。我稍后分析DownloadImage()方法,是重点之一                     downloadThread  =   new  Thread( new  ThreadStart(DownloadImage));                    downloadThread.Name  =   " ImageLayer.DownloadImage " ;                                        downloadThread.IsBackground  =   true ;                    downloadThread.Start();                     return ;                }                 if (m_TextureStream  !=   null )                {             //从文件流中,更新Texture(纹理),重点分析                     UpdateTexture(m_TextureStream, m_TransparentColor);                    verticalExaggeration  =  World.Settings.VerticalExaggeration;             //创建Mesh,稍后重点分析                     CreateMesh();                    isInitialized  =   true ;                     return ;                }                 else   if (imageFileInfo  !=   null   &&  imageFileInfo.Exists)                {           //从文件路径中更新纹理                    UpdateTexture(_imagePath);                    verticalExaggeration  =  World.Settings.VerticalExaggeration;           //创建格网Mesh,为了获取Vertex集合。                    CreateMesh();                    isInitialized  =   true ;                     return ;                }                 if (_imageUrl  !=   null   &&  _imageUrl.ToLower().StartsWith( " http:// " ))                {                     // download it...                     downloadThread  =   new  Thread( new  ThreadStart(DownloadImage));                    downloadThread.Name  =   " ImageLayer.DownloadImage " ;                                        downloadThread.IsBackground  =   true ;                    downloadThread.Start();                     return ;                }                 //  No image available                 Dispose();                isOn  =   false ;                 return ;            }             catch             {            }        }

 

398行DownloadImage()方法中关键代码分析学习

关键代码          //WW中负责从Web下载文件(影像和配置文件)的类WebDownload 。(写的很好,可以被我们重用。这里是个知识点,感兴趣地自己学习)             using (WebDownload downloadReq  =   new  WebDownload( this ._imageUrl))                {                    downloadReq.ProgressCallback  +=   new  DownloadProgressHandler(UpdateDownloadProgress);                     string  filePath  =  getFilePathFromUrl(_imageUrl);                                         if (_imagePath == null )                    {                         //  Download to RAM               //将下载的文件放到内存中,然后ImageHelper类从流对象加载转变为纹理对象。                         downloadReq.DownloadMemory();                        texture  =  ImageHelper.LoadTexture(downloadReq.ContentStream);                    }                     else                     {                      // 将加载的文件保存到——imagePath中,然后加载更新纹理                         downloadReq.DownloadFile(_imagePath);                         UpdateTexture(_imagePath);                     }              //创建Mesh(我是自学DirectX,搞不清Mesh该翻译成啥,但我知道是干啥用的)                     CreateMesh();                    isInitialized  =   true ;                }

 

ImageHelper中加载纹理的函数真正实现是191行public static Texture LoadTexture(Stream textureStream, int colorKey)。

从流中加载纹理代码 public   static  Texture LoadTexture(Stream textureStream,  int  colorKey)        {             try             { //Direct3D的TextureLoader类的FromStream,从流对象中创建纹理                 Texture texture  =  TextureLoader.FromStream(DrawArgs.Device, textureStream,  0 0 ,                     1 , Usage.None, World.Settings.TextureFormat, Pool.Managed, Filter.Box, Filter.Box, colorKey);                 return  texture;            }             catch  (Microsoft.DirectX.Direct3D.InvalidDataException)            {            }             try             {                 //  DirectX failed to load the file, try GDI+                 //  Additional formats supported by GDI+: GIF, TIFF                  //  TODO: Support color keying.  See: System.Drawing.Imaging.ImageAttributes           如果DirectX创建纹理失败,则使用GDI+通过创建Image来实现创建纹理                  using  (Bitmap image  =  (Bitmap)Image.FromStream(textureStream))                {            //学过DirectX,下面创建纹理的方法在Demo中很常见(一定要先学DirectX编程)                     Texture texture  =   new  Texture(DrawArgs.Device, image, Usage.None, Pool.Managed);                     return  texture;                }            }             catch             {                 throw   new  Microsoft.DirectX.Direct3D.InvalidDataException( " Error reading image stream. " );            }        }

 

通过文件路径中加载纹理,最终也是调用上面的方法,只是先通过文件路径,将文件打开创建了流对象。请看ImageHelper.cs中52行public static Texture LoadTexture(string textureFileName, int colorKey)。

  public static Texture LoadTexture(string textureFileName, int colorKey)  {   try   {

//将文件打开创建了流对象,从影像流中创建纹理    using (Stream imageStream = File.OpenRead(textureFileName))     return LoadTexture(imageStream, colorKey);   }   catch   {    throw new Microsoft.DirectX.Direct3D.InvalidDataException(string.Format("Error reading image file '{0}'.", textureFileName));   }  }

ImageLayer.cs中的956行代码UpdateTexture(string fileName)更新纹理方法,关键代码Texture newTexture = ImageHelper.LoadTexture(fileName);就是从文件路径中创新新的纹理对象,上面已经分析过了,不再赘述。

CreateMesh()方法分析

该方法是最关键的,它创建了Device最终渲染三角面列表所需要的点集合,即 protected CustomVertex.PositionNormalTextured[] vertices;其中CustomVertex.PositionNormalTextured是点类型,就是说所要绘制的点是带纹理的带法线的。(如果要问为什么要这样,那请你先学习好DirectX编程)

 

构建Mesh代码      protected   virtual   void  CreateMesh()        {             int  upperBound  =  meshPointCount  -   1 ;             float  scaleFactor  =  ( float ) 1 / upperBound;             double  latrange  =  Math.Abs(maxLat  -  minLat);             double  lonrange;             if (minLon  <  maxLon)                lonrange  =  maxLon  -  minLon;             else                 lonrange  =   360.0f   +  maxLon  -  minLon;             int  opacityColor  =  System.Drawing.Color.FromArgb( this .m_opacity, 0 , 0 , 0 ).ToArgb();            //创建点集合对象vertices,大小为64*64个             vertices  =   new  CustomVertex.PositionNormalTextured[meshPointCount  *  meshPointCount];             for ( int  i  =   0 ; i  <  meshPointCount; i ++ )            {                 for ( int  j  =   0 ; j  <  meshPointCount; j ++ )                {                         double  height  =   0 ;                     if ( this ._terrainAccessor  !=   null )                        height  =   this .verticalExaggeration  *   this ._terrainAccessor.GetElevationAt(                            ( double )maxLat  -  scaleFactor  *  latrange  *  i,                            ( double )minLon  +  scaleFactor  *  lonrange  *  j,                            ( double )upperBound  /  latrange);           //将空间坐标转换为矢量坐标(即经纬度和半径,转为X\Y\Z)                      Vector3 pos  =  MathEngine.SphericalToCartesian(                          maxLat  -  scaleFactor * latrange * i,                         minLon  +  scaleFactor * lonrange * j,                          layerRadius  +  height);                      //获取点的X,Y,Z Tu Tv                     vertices[i * meshPointCount  +  j].X  =  pos.X;                    vertices[i * meshPointCount  +  j].Y  =  pos.Y;                    vertices[i * meshPointCount  +  j].Z  =  pos.Z;                                        vertices[i * meshPointCount  +  j].Tu  =  j * scaleFactor;                    vertices[i * meshPointCount  +  j].Tv  =  i * scaleFactor;                 //     vertices[i*meshPointCount + j].Color = opacityColor;                 }            }         //下面的代码是将上面的vertices集合,构建成6个一组。(注:DirectX中绘制面是绘制两个三角形,是要存六个点的)             indices  =   new   short [ 2   *  upperBound  *  upperBound  *   3 ];             for ( int  i  =   0 ; i  <  upperBound; i ++ )            {                 for ( int  j  =   0 ; j  <  upperBound; j ++ )                {                    indices[( 2 * 3 * i * upperBound)  +   6 * j]  =  ( short )(i * meshPointCount  +  j);                    indices[( 2 * 3 * i * upperBound)  +   6 * +   1 =  ( short )((i + 1 ) * meshPointCount  +  j);                    indices[( 2 * 3 * i * upperBound)  +   6 * +   2 =  ( short )(i * meshPointCount  +  j + 1 );                        indices[( 2 * 3 * i * upperBound)  +   6 * +   3 =  ( short )(i * meshPointCount  +  j + 1 );                    indices[( 2 * 3 * i * upperBound)  +   6 * +   4 =  ( short )((i + 1 ) * meshPointCount  +  j);                    indices[( 2 * 3 * i * upperBound)  +   6 * +   5 =  ( short )((i + 1 ) * meshPointCount  +  j + 1 );                }            }           //计算面的法向量(是重点,高等数学好的话,可以自己学习一下,看看如何求的单位法向量),不想看,调用该方法就行,知道干啥用和怎么用就行啦             calculate_normals( ref  vertices, indices);        }

 Render()方法分析

 ImageLayer.cs中651行

 

Render()代码          public   override   void  Render(DrawArgs drawArgs)        {             if (downloadThread  !=   null   &&  downloadThread.IsAlive) //DirectX知识点,可以学习一下                  RenderProgress(drawArgs );             if ( ! this .isInitialized)                 return ;             try             {                                 if (texture  ==   null   ||  m_SurfaceImage  !=   null )                     return ;         //这里是DirectX渲染的关键点之一, 设置渲染纹理。                 drawArgs.device.SetTexture( 0this .texture);                 if ( this ._disableZbuffer)                {                     if (drawArgs.device.RenderState.ZBufferEnable)                        drawArgs.device.RenderState.ZBufferEnable  =   false ;                }                 else                 {                     if ( ! drawArgs.device.RenderState.ZBufferEnable)                        drawArgs.device.RenderState.ZBufferEnable  =   true ;                }                drawArgs.device.RenderState.ZBufferEnable  =   true ;                drawArgs.device.Clear(ClearFlags.ZBuffer,  0 1.0f 0 );          //设置device.Transform.World                 drawArgs.device.Transform.World  =  Matrix.Translation(                        ( float ) - drawArgs.WorldCamera.ReferenceCenter.X,                        ( float ) - drawArgs.WorldCamera.ReferenceCenter.Y,                        ( float ) - drawArgs.WorldCamera.ReferenceCenter.Z                        );           //设置顶点格式为CustomVertex.PositionNormalTextured.Format                 device.VertexFormat  =  CustomVertex.PositionNormalTextured.Format;          //device.DeviceCaps.PixelShaderVersion.Major 获取DirectX中Device的PixelShaderVersion实现主版本号,可以学习一下(device.DeviceCaps)                  if  ( ! RenderGrayscale  ||  (device.DeviceCaps.PixelShaderVersion.Major  <   1 ))                {                     if  (World.Settings.EnableSunShading)                    {          //获取太阳在三维空间的照射位置点                         Point3d sunPosition  =  SunCalculator.GetGeocentricPosition(TimeKeeper.CurrentTimeUtc);                //转换为笛卡尔坐标系下的矢量点                         Vector3 sunVector  =   new  Vector3(                            ( float )sunPosition.X,                            ( float )sunPosition.Y,                            ( float )sunPosition.Z); //以下都是设置Device的渲染参数的,DirectX编程中很常见的                //使用灯光                         device.RenderState.Lighting  =   true ;                        Material material  =   new  Material();                        material.Diffuse  =  System.Drawing.Color.White;                        material.Ambient  =  System.Drawing.Color.White;               //设置材料Material                         device.Material  =  material;                        device.RenderState.AmbientColor  =  World.Settings.ShadingAmbientColor.ToArgb();                //使用法向量                        device.RenderState.NormalizeNormals  =   true ;                        device.RenderState.AlphaBlendEnable  =   true ;               //设置灯光参数                         device.Lights[ 0 ].Enabled  =   true ;                        device.Lights[ 0 ].Type  =  LightType.Directional;                        device.Lights[ 0 ].Diffuse  =  System.Drawing.Color.White;                //灯光方向,来自太阳                         device.Lights[ 0 ].Direction  =  sunVector;              //设置纹理参数                        device.TextureState[ 0 ].ColorOperation  =  TextureOperation.Modulate;                        device.TextureState[ 0 ].ColorArgument1  =  TextureArgument.Diffuse;                        device.TextureState[ 0 ].ColorArgument2  =  TextureArgument.TextureColor;                    }                     else                     {                        device.RenderState.Lighting  =   false ;                        device.RenderState.Ambient  =  World.Settings.StandardAmbientColor;                        drawArgs.device.TextureState[ 0 ].ColorOperation  =  TextureOperation.SelectArg1;                        drawArgs.device.TextureState[ 0 ].ColorArgument1  =  TextureArgument.TextureColor;                    }                    device.RenderState.TextureFactor  =  System.Drawing.Color.FromArgb(m_opacity,  0 0 0 ).ToArgb();                    device.TextureState[ 0 ].AlphaOperation  =  TextureOperation.BlendFactorAlpha;                    device.TextureState[ 0 ].AlphaArgument1  =  TextureArgument.TextureColor;                    device.TextureState[ 0 ].AlphaArgument2  =  TextureArgument.TFactor;                    drawArgs.device.VertexFormat  =  CustomVertex.PositionNormalTextured.Format;             //Device实现渲染绘制,调用的是DrawIndexedUserPrimitives。(如果看不懂,请参考DirectX编程相关知识)                      drawArgs.device.DrawIndexedUserPrimitives(PrimitiveType.TriangleList,  0 ,                         vertices.Length, indices.Length  /   3 , indices,  true , vertices);                                     }                 else                 {           //以下是使用Effect绘制的                      if  (grayscaleEffect  ==   null )                    {                        device.DeviceReset  +=   new  EventHandler(device_DeviceReset);                        device_DeviceReset(device,  null );                    }            //设置Effect对象参数                     grayscaleEffect.Technique  =   " RenderGrayscaleBrightness " ;                    grayscaleEffect.SetValue( " WorldViewProj " , Matrix.Multiply(device.Transform.World, Matrix.Multiply(device.Transform.View, device.Transform.Projection)));                    grayscaleEffect.SetValue( " Tex0 " , texture);                    grayscaleEffect.SetValue( " Brightness " , GrayscaleBrightness);                     float  opacity  =  ( float )m_opacity  /   255.0f ;                    grayscaleEffect.SetValue( " Opacity " , opacity);                 //以下是Effect渲染的关键                     int numPasses = grayscaleEffect.Begin(0 );                      for  ( int  i  =   0 ; i  <  numPasses; i ++ )                    {                          grayscaleEffect.BeginPass(i);                           drawArgs.device.DrawIndexedUserPrimitives(PrimitiveType.TriangleList,  0 ,                         vertices.Length, indices.Length  /   3 , indices,  true , vertices );                         grayscaleEffect.EndPass();                     }                     grayscaleEffect.End();                 }                drawArgs.device.Transform.World  =  drawArgs.WorldCamera.WorldMatrix;                            }             finally             {                 if  (m_opacity  <   255 )                {                     //  Restore alpha blend state                     device.RenderState.SourceBlend  =  Blend.SourceAlpha;                    device.RenderState.DestinationBlend  =  Blend.InvSourceAlpha;                }                 if ( this ._disableZbuffer)                    drawArgs.device.RenderState.ZBufferEnable  =   true ;            }        }

 

我想通过分析上面的,让大家看看Render()方法渲染的流程,里面的具体渲染知识点大家可以参看一些DirectX编程。因为Update()函数中调用的方法在上面都有介绍,所以就不再另外分析啦。希望对大家深入了解学习WW插件渲染有帮助。

 

 本系列其他部分:

WorldWind学习系列九:Blue Marble插件学习

WorldWind学习系列八:Load/Unload Plugins——直捣黄龙篇

WorldWind学习系列七:Load/Unload Plugins——投石问路篇

WorldWind学习系列六:渲染过程解析篇

WorldWind学习系列五:插件加载过程全解析

WorldWind学习系列四:功能分析——Show Planet Axis、Show Position 、Show Cross Hairs功能

WorldWind学习系列三:简单功能分析——主窗体的键盘监听处理及拷贝和粘贴位置坐标功能

WorldWind学习系列三:功能分析——截屏功能和“关于”窗体分析

WorldWind学习系列二:擒贼先擒王篇2

WorldWind学习系列二:擒贼先擒王篇1

WorldWind学习系列一:顺利起航篇

 

 

 

 

 

转载于:https://www.cnblogs.com/wuhenke/archive/2009/12/21/1629339.html

最新回复(0)