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


      学习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, 00,
                        
    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, 
    01.0f0);
             
    //设置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, 000).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学习系列一:顺利起航篇

     

     

  • 相关阅读:
    Windows Server 2012 R2 里面如何安装Net Framework 3.5
    虚拟机网络驱动(共享文件夹)不见了的解决方案-适用于win7~win10 and Windows Server 2008~Windows Server 2012R2
    在计算机 . 上没有找到服务 WAS
    免费获取WP之类的开发者权限或免费使用Azure 2015-10-19
    颠覆你的认知,带你领略史上最为齐全的微软黑科技之旅
    【技巧】只利用 Visual Stdio 自带的工具这么找父类?
    网站定位之---根据IP获得区域
    06.移动先行之谁主沉浮----我的代码我来写(Xaml的优势)
    05.移动先行之谁主沉浮----小应用的美化
    04.移动先行之谁主沉浮----XAML的探索
  • 原文地址:https://www.cnblogs.com/wuhenke/p/1629339.html
Copyright © 2020-2023  润新知