学习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 * 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 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