WorldWind系列十:RendableObject中的DirectX渲染分析:ImageLaye -电脑资料

电脑资料 时间:2019-01-01 我要投稿
【www.unjs.com - 电脑资料】

学习WW有一段时间了,但是若想开发自己基于WW的插件,必然会遇到RendableObject中的DirectX渲染问题,

WorldWind系列十:RendableObject中的DirectX渲染分析:ImageLaye

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

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

注:请务必先学过

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

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

Initialize方法代码

/// 


         /// Layer initialization code
         /// 

         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 pacityColor = 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个一组,

电脑资料

WorldWind系列十:RendableObject中的DirectX渲染分析:ImageLaye》(https://www.unjs.com)。(注: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*j + 1] = (short)((i+1)*meshPointCount + j);
                     indices[(2*3*i*upperBound) + 6*j + 2] = (short)(i*meshPointCount + j+1);

                     indices[(2*3*i*upperBound) + 6*j + 3] = (short)(i*meshPointCount + j+1);
                     indices[(2*3*i*upperBound) + 6*j + 4] = (short)((i+1)*meshPointCount + j);
                     indices[(2*3*i*upperBound) + 6*j + 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(0, this.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 pacity = (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插件渲染有帮助。

最新文章