学习WW有一段时间了,但是若想开发自己基于WW的插件,必然会遇到RendableObject中的DirectX渲染问题。所有需要渲染绘制的WW三维插件,最终是通过继承RendableObject并实现自己的Initialize()、Update()、Render()方法的。想写自己的Render()方法不是简单的事情,你必然要学习DirectX编程,否则,你连看懂示例中的底层Render()方法都很难,谈何开发自己的插件。
为了突破DirectX编程对我学习WW插件的阻挠,我“快餐式”地突击学习了DirectX,对基本流程和基本原理有了一定认识。今天下午我也对BMNG插件中RendableObject子类——ImageLayer为例进行了分析学习。现在与大家分享一下。
注:请务必先学过Direct3D学习(资料收集),否则看下面的内容无异于浪费你宝贵的时间。
Image Layer重载实现的Initialize()方法。


/// Layer initialization code
/// </summary>
public override void Initialize(DrawArgs drawArgs)
{
try
{
this .device = drawArgs.device;
if (_imagePath == null && _imageUrl != null && _imageUrl.ToLower().StartsWith( " http:// " ))
{
}
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
downloadThread = new Thread( new ThreadStart(DownloadImage));
downloadThread.Name = " ImageLayer.DownloadImage " ;
downloadThread.IsBackground = true ;
downloadThread.Start();
return ;
}
if (m_TextureStream != null )
{
UpdateTexture(m_TextureStream, m_TransparentColor);
verticalExaggeration = World.Settings.VerticalExaggeration;
CreateMesh();
isInitialized = true ;
return ;
}
else if (imageFileInfo != null && imageFileInfo.Exists)
{
UpdateTexture(_imagePath);
verticalExaggeration = World.Settings.VerticalExaggeration;
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()方法中关键代码分析学习


{
downloadReq.ProgressCallback += new DownloadProgressHandler(UpdateDownloadProgress);
string filePath = getFilePathFromUrl(_imageUrl);
if (_imagePath == null )
{
// Download to RAM
downloadReq.DownloadMemory();
texture = ImageHelper.LoadTexture(downloadReq.ContentStream);
}
else
{
downloadReq.DownloadFile(_imagePath);
UpdateTexture(_imagePath);
}
CreateMesh();
isInitialized = true ;
}
ImageHelper中加载纹理的函数真正实现是191行public static Texture LoadTexture(Stream textureStream, int colorKey)。


{
try
{
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
using (Bitmap image = (Bitmap)Image.FromStream(textureStream))
{
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编程)


{
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 = 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行


{
if (downloadThread != null && downloadThread.IsAlive)
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
( float ) - drawArgs.WorldCamera.ReferenceCenter.X,
( float ) - drawArgs.WorldCamera.ReferenceCenter.Y,
( float ) - drawArgs.WorldCamera.ReferenceCenter.Z
);
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.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
{
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学习系列四:功能分析——Show Planet Axis、Show Position 、Show Cross Hairs功能
WorldWind学习系列三:简单功能分析——主窗体的键盘监听处理及拷贝和粘贴位置坐标功能
WorldWind学习系列三:功能分析——截屏功能和“关于”窗体分析