Silverlight 5 beta 版本的公布, 其支持3d 效果到底如何。本系列文章将和大家一起学习研究。
Silverlight 5 目前处于Beta版本,如果是 Visual Studio 2010,需要先安装Visual Studio SP1,然后才能安装 Silverlight 5 开发工具包。由于Silverlight 5 在测试,可能得不到版本支持------即浏览一个Silverlight 5 网站,只能手动安装 Silverlight 开发版本来浏览。
地址:http://www.silverlight.net/getstarted/silverlight-5-beta/
在Silverlight 5 帮助文档里,给出了3d渲染的基本步骤:
1.开启GPU加速,即设置EnableGPUAcceleration 为 true
2.添加DrawSurface控件同时定义Draw事件
3.生成Draw事件处理函数
4.定义一个存储顶点数据的结构体。(包括VertexDeclartion)
5.为3D图形定义 顶点数据
6.定义VertexBuffer并使用SetData(T)来添加顶点数据
7.定义PixelShader和VertexShader对象
8.如果你的顶点着色器需要矩阵,定义world-view,Camera view, Projection space矩阵
9.在Draw事件里
a.调用Clear方法清空GraphicsDevice
b.使用SetVertexBuffer将VertexBuffer设置到图形设备上
c.使用SetVertexShader将VertexShader设置到图形设备上
使用SetVertexShaderConstantFloat4(T)传递一个矩阵参数到顶点着色器
d.使用SetPixelShadeer将PixelShader设置到图形设备上
e.调用DrawPrimitives
f.可以通过调用InvalidateSurface触发另一个绘图事件
10.在图形线程无效的表面上调用Invalidate;
Silverlight 5 依靠DrawingSurface 这个新控件来渲染3d 图形。并且增加了一些新类来支持3d。通过引进一个轻量级的XNA Graphics Framework 4.0提供对3D的支持。这个特性可以开发更丰富的游戏和用户体验。
以下是Silverlight 命名空间中 3d 相关的类。
Namespace |
Notes |
包含类 Color 和 Rectangle 结构被Silverlight 3D 图形类使用. 注意:这2个结构和 System.Windows.Media.Color 以及 System.Windows.Shapes.Rectangle 类是不一样的。 |
|
包含了大多数 XNA 3D 图形类。 |
|
Microsoft.Xna.Framework.Silverlight |
包含了Silverlight 特有的3d 类. |
包含了 DrawingSurface 和DrawEventArgs. |
|
BitmapSource 类被BitmapSourceExtensions 扩展了一些 CopyTo 方法用来从位图图片中创建 Texture 。 |
Silverlight 只支持 Reach profile 模式,这个模式需要显卡支持Shader model2.0+以及DirectX 9+,关于该模式一些参数,可以查看下表(该表取至Xna):
Shader model (着色器 版本) |
2.0 |
Max texture size (最大纹理) |
2048 |
Max cubemap size (最大多面体) |
512 |
Max volume texture size (最大体积纹理) |
不支持 |
Non power of two textures (无二次幂限制的纹理) 解释:OpenGL仅支持分辨率为2mx2n的纹理 |
限制: 不能用于环绕 寻址模式, 纹理链, 或 DXT 压缩尺寸不是二次幂 |
Non power of two cubemaps (无二次幂限制的多面体) |
不支持 |
Non power of two volume textures (无二次幂限制的体积纹理) |
体积纹理不支持 |
Max primitives per draw call (最大每秒调用绘制) |
65535 |
Index buffer formats (缓冲索引格式) |
16 bit |
Vertex element formats (顶点元素格式) |
Color, Byte4, Single, Vector2, Vector3, Vector4, Short2, Short4, NormalizedShort2, NormalizedShort4 |
Texture formats (纹理格式) |
Color, Bgr565, Bgra5551, Bgra4444, NormalizedByte2, NormalizedByte4, Dxt1, Dxt3, Dxt5 |
Vertex texture formats (顶点纹理格式) |
Vertex texturing is not supported |
Render target formats (渲染目标格式) |
部分支持 (见下) |
Multiple render targets (多次渲染目标) |
不支持 |
Occlusion queries (遮挡测试) |
不支持 |
Separate alpha blend |
不支持 |
Blend.SourceAlphaSaturation |
只支持SourceBlend, 不支持 DestinationBlend |
Max vertex streams |
16 |
Max stream stride |
255 |
下面来开始一个简单的3d 例子:
首先创建一个Silverlight 工程项目。
第一步:将 <param name="EnableGPUAcceleration" value="true" /> 添加到网页的Silverlight 插件对象设置中来开启硬件加速。
第二步:在主控件的xaml 里添加 DrawingSurface,并定义了Draw事件处理函数DrawingSurface_Draw。
< Grid x:Name ="LayoutRoot" Background ="Black" >
< DrawingSurface Draw ="DrawingSurface_Draw"
Width ="500"
Height ="500" />
</ Grid >
第三步: 在没有编写Draw事件处理函数DrawingSurface_Draw 的内容之前,在xmal里添加一个TextBlock来显示程序的fps情况
< TextBlock x:Name ="txt" VerticalAlignment ="Top" HorizontalAlignment ="Left" Foreground ="White" />
private void DrawingSurface_Draw(object sender, DrawEventArgs e)
{
txt.Dispatcher.BeginInvoke(() =>
{
txt.Text = string.Format("刷新频率: {0}ms\t 总运行时间: {1}", e.DeltaTime.TotalMilliseconds, e.TotalTime);
});
}
运行后,刷新频率没有值因为Draw事件仅仅被命中一次,DrawEventArgs使用InvalidateSurface 方法来强制刷新DrawingSurface控件同时再次命中Draw事件。
修改成以下代码再运行:
private void DrawingSurface_Draw( object sender, DrawEventArgs e)
{
txt.Dispatcher.BeginInvoke(() =>
{
txt.Text = string .Format( " 刷新频率: {0}ms\t 总运行时间: {1} " , e.DeltaTime.TotalMilliseconds, e.TotalTime);
});
e.InvalidateSurface();
}
我的机器上大概是 17ms ,也就是fps 在60 左右。
再添加一个TextBlock来显示程序的硬件加速情况,然后在MainPage 的构造函数里添加以下代码:
GraphicsDeviceManager deviceManager = GraphicsDeviceManager.Current;
txt1.Text = string .Format( " 渲染模式: {0}\t 渲染结果: {1} " , deviceManager.RenderMode, deviceManager.RenderModeReason);
RenderMode 只有2个参数,未定义模式和硬件模式。注意:Silverlight 3D 不支持软件渲染模式,这里的 Unavailable 不代表软件模式。
RenderModeReason 有好几个理由,分别是正常,GPU加速未开启,GPU加速可能在浏览器中禁用,硬件不支持3d,设备暂时不可用。
第四步:3d中一个图形绘制分为顶点渲染和像素渲染(也叫顶点着色和像素着色),顶点渲染主要负责描绘图形,也就是建立模形。像素渲染主要负责把顶点绘出的图形填上填色。然后再加上纹理贴图单元贴上纹理。
绘制的第一步就是为显卡提供顶点数据。顶点数据存储在结构体里,可以自定义格式,但是必须遵循一定的规则。一个顶点数据的结构保存了顶点的数据(空间位置,颜色,贴图座标等等信息)和一个VertexDeclaration对象来告诉设备数据的大小和如何格式化数据。
下面来定义一个顶点数据结构,Silverlight 类库中没有提供 Vector3 等辅助类,需要去以下地址下载Microsoft.Xna.Framework.Math.dll
地址:http://code.msdn.microsoft.com/XNA-Math-Helper-DLL-d4d1f7d4
引用到项目里,构建顶点数据结构如下:
public struct VertexPositionColor
{
public Vector3 Position;
public Microsoft.Xna.Framework.Color Color;
public VertexPositionColor(Vector3 position, Microsoft.Xna.Framework.Color color)
{
Position = position; Color = color;
}
public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration(
new VertexElement( 0 , VertexElementFormat.Vector3, VertexElementUsage.Position, 0 ),
new VertexElement( 12 , VertexElementFormat.Color, VertexElementUsage.Color, 0 ));
}
这个是一个帮助文档里示例的简单顶点数据结构,包括了一个位置,颜色,和VertexDeclaration对象。
第5步:绘制一个3角形,用前面的定义的顶点数据结构来构造,并填充到顶点数据缓冲VertexBuffer里。
private VertexBuffer CreateTriangle()
{
VertexPositionColor[] vertices = new VertexPositionColor[ 3 ];
Microsoft.Xna.Framework.Color color = new Microsoft.Xna.Framework.Color( 255 , 255 , 255 , 255 );
vertices[ 0 ].Position = new Vector3( - 1 , - 1 , 0 ); // left
vertices[ 1 ].Position = new Vector3( 0 , 1 , 0 ); // top
vertices[ 2 ].Position = new Vector3( 1 , - 1 , 0 ); // right
vertices[ 0 ].Color = color;
vertices[ 1 ].Color = color;
vertices[ 2 ].Color = color;
VertexBuffer vb = new VertexBuffer(
GraphicsDeviceManager.Current.GraphicsDevice,
VertexPositionColor.VertexDeclaration,
vertices.Length,
BufferUsage.WriteOnly);
vb.SetData( 0 , vertices, 0 , vertices.Length, 0 );
return vb;
}
Fxc.exe /T vs_2_0 /O3 /Zpr /Fo Triangle.vs Triangle.vs.hlsl
Fxc.exe /T ps_2_0 /O3 /Zpr /Fo Triangle.ps Triangle.ps.hlsl
这里,要了解的是着色程序是由图形设备执行,由于Shader指令集非常复杂,因此出现了hlsl这样的面向过程的Shader语言,来简化程序员的工作。 hlsl 编写完后需要被编译成显卡的GPU指令序列来让显卡进行顶点处理过程和像素处理过程。所以,编写hlsl时应该尽量简洁高效。
关于更详细的hlsl 请自行寻找相关资料,这里我直接使用了帮助文档里的hlsl.
为了更好的效率,我把创建3角形顶点数据和构建着色对象放在了DrawingSurface 的加载事件中。
private void DrawingSurface_Loaded( object sender, RoutedEventArgs e)
{
GraphicsDevice resourceDevice = GraphicsDeviceManager.Current.GraphicsDevice;
vertexBuffer = CreateTriangle();
Stream shaderStream = Application.GetResourceStream( new Uri( @" Silverlight3DGame1;component/Triangle.vs " , UriKind.Relative)).Stream;
vertexShader = VertexShader.FromStream(resourceDevice, shaderStream);
shaderStream = Application.GetResourceStream( new Uri( @" Silverlight3DGame1;component/Triangle.ps " , UriKind.Relative)).Stream;
pixelShader = PixelShader.FromStream(resourceDevice, shaderStream);
}
第7步:设置相机和投影矩阵。(以后继续学习)
private void SetMatrix()
{
Vector3 cameraPosition = new Vector3( 0 , 0 , 5.0f ); // 相机坐标
Vector3 cameraTarget = Vector3.Zero; // 相机朝向 (朝向世界坐标原点)
// 相机的视口
view = Microsoft.Xna.Framework.Matrix.CreateLookAt(cameraPosition, cameraTarget, Vector3.Up);
projection = Microsoft.Xna.Framework.Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 1.667f , 1.0f , 10.0f );
}
private void Draw(GraphicsDevice graphicsDevice)
{
graphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Microsoft.Xna.Framework.Color.Transparent, 1.0f , 0 );
graphicsDevice.SetVertexBuffer(vertexBuffer);
graphicsDevice.SetVertexShader(vertexShader);
Microsoft.Xna.Framework.Matrix viewProjection = view * projection;
graphicsDevice.SetVertexShaderConstantFloat4( 0 , ref viewProjection);
graphicsDevice.SetPixelShader(pixelShader);
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0 , 2 );
}
最后,即可在屏幕上画出一个白色三角形。在代码里还有一个绘制矩形的函数,大家可以更换一下,查看下效果,并自己尝试绘制其它图形。