SilverXna初体验:SpriteBatch和基本的内容管道
2011-09-03 19:39 独孤残云 阅读(2818) 评论(14) 编辑 收藏 举报昨天,各大IT网站纷纷刊登了Silverlight5 RC发布的消息。于是,第一时间到官网载了安装包,更新了本地的Silverlight5 Beta版本~
Silverlight5 RC的发布无疑是具有里程碑意义的,Xna3D API也在原有Beta版本基础上作了进一步扩展。
新增了BasicEffect、RenderTarget等Shader常用功能,上一节提到的Xna 3D数学库也被划入到Sliverlight原生资源当中,无需再从外部引用。
不过,目前SilverXna中的一系列绘制函数依然停留在顶点级,缺乏诸如SpriteBatch、ContentManager、Model等必要高级机制的支持。
曾一度感觉无奈,直到看过trcj兄编写的ElGameEngine之后才恍然大悟:所谓3D,只要显卡能画三角形(硬件加速),支持矩阵运算(顶点着色),支持纹理采样(像素着色),再给几个API其实就够用了。
本节,我们来自行实现SpriteBatch的相关功能,以供SilverXna中简单的2D图形绘制之用~
熟知3DGraphy机制的人应该都知道,3D领域中的2D,其实只是3D图元绘制的一种特例:两个三角形对接构成一个矩形表面,而后由目标纹理采样得到各点颜色。至于顶点运算的部分,世界矩阵及摄影矩阵固定使用单位矩阵,投影矩阵采用正交投影替代原有的透视投影即可。
大家还记得Direct3D轮回《为D3D量身定做SpriteBatch》一文吗?它其实就是对2D图形原理的一个很好的说明~
下面,我们就把Direct3D中的代码搬到Silverlight里,得到SilverXna专用的SpriteBatch对象~
由于Xna已经彻底舍弃了固定功能流水线(完全硬件加速),而SpriteBatch的绘制并不需要用到BasicEffect中的诸多功能,因此我们不妨自己来编写Shader。
Silverlight5 RC初步支持了效果框架,BasicEffect使用过程中对于EffectTechniquehe和Pass的解析均是标准而到位的。
不过,由Effect的定义来推断,我们似乎还不能随心所欲的引入外部的.fx到Silverlight。以下依然沿用Beta版的做法~
编写顶点着色器:
代码清单:SpriteBatch.vs.hlsl
来自:http://www.cnblogs.com/kenkao
-------------------------------------*/
// 世界·摄影·投影变换矩阵
float4x4 WorldViewProj : register(c0);
// 顶点着色器输入结构
struct VertexData
{
float3 Position : POSITION; // 位置
float4 Color : COLOR; // 颜色
float2 UV : TEXCOORD; // 纹理坐标
};
// 顶点着色器输出结构
struct VertexShaderOutput
{
float4 Position : POSITION;
float4 Color : COLOR0;
float2 UV : TEXCOORD0;
};
VertexShaderOutput main(VertexData vertex)
{
VertexShaderOutput output;
output.Position = mul(float4(vertex.Position,1), WorldViewProj); // 顶点位置变换
output.Color = vertex.Color; // 传递颜色
output.UV = vertex.UV; // 传递纹理坐标
return output;
}
编写像素着色器:
代码清单:SpriteBatch.ps.hlsl
来自:http://www.cnblogs.com/kenkao
-------------------------------------*/
// 目标纹理及采样器
texture cubeTexture : register(t0);
sampler cubeSampler = sampler_state
{
texture = <cubeTexture>;
};
// 像素着色器输入结构
struct VertexShaderOutput
{
float4 Color : COLOR0;
float2 UV : TEXCOORD0;
};
float4 main(VertexShaderOutput vertex) : COLOR
{
return vertex.Color *= tex2D(cubeSampler, vertex.UV).rgba; // 返回颜色
}
接下来,我们要使用DirectX命令行工具对其进行编译,得到顶点着色器及像素着色器可用的二进制文件~
微软Silverlight5官方实例中为我们封装了两个批处理文件,可用于x64和x86机型顶点着色器及像素着色器的编译生成~
>> 点击下载:
执行相应的批处理文件:
则我们将得到3个新文件:
SpriteBatch.vs(顶点着色器)
SpriteBatch.ps(像素着色器)
hlslcomplog.txt(编译日志)
我们将得到的SpriteBatch.vs和SpriteBatch.ps引入工程,而后将其属性设置为Resource即可~
完成Shader之后就可以开始着手编写SpriteBatch主体代码了~
首先是顶点结构定义:
{
public Vector3 _Position; // 位置
public Color _Color; // 颜色
public Vector2 _UV; // 纹理坐标
// 构造函数
public VertexPositionColorTexture(Vector3 position, Color color, Vector2 uv)
{
_Position = position;
_Color = color;
_UV = uv;
}
// 顶点声明
public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration(
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0),
new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0)
);
}
接下来是SpriteBatch的编写:
代码清单:SpriteBatch.cs
来自:http://www.cnblogs.com/kenkao
-------------------------------------*/
using System;
using System.Net;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media.Animation;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
namespace Microsoft.Xna.Framework.Graphics
{
public class SpriteBatch
{
struct SpriteNode
{
public Rectangle _DesRect; // 目标区域
public Rectangle _SurRect; // 纹理区域
public float _layerDepth; // 深度(Z坐标)
public Color _Color; // 色相
public SpriteNode(Rectangle DesRect, Rectangle SurRect, float layerDepth, Color Color)
{
_DesRect = DesRect;
_SurRect = SurRect;
_layerDepth = layerDepth;
_Color = Color;
}
}
Texture2D _ActiveTexture; // 活动纹理
VertexShader _VertexShader; // 顶点着色器
PixelShader _PixelShader; // 像素着色器
Matrix _ViewMatrix; // 摄影矩阵
Matrix _ProjMatrix; // 投影矩阵
List<SpriteNode> _SpriteNodeList; // 精灵节点列表
SilverGame _SilverGame; // SilverGame实体
public SilverGame SilverGame
{ get { return _SilverGame; } }
/// <summary>
/// 构造方法
/// </summary>
/// <param name="game">SilverGame实体对象</param>
public SpriteBatch(SilverGame game)
{
_SilverGame = game;
_SpriteNodeList = new List<SpriteNode>();
// 加载SpriteBatch顶点着色器及像素着色器
Stream shaderStream = Application.GetResourceStream(new Uri(@"SilverXna.Game.Library;component/SpriteBatch/SpriteBatch.vs", UriKind.Relative)).Stream;
_VertexShader = VertexShader.FromStream(_SilverGame.GraphicsDevice, shaderStream);
shaderStream = Application.GetResourceStream(new Uri(@"SilverXna.Game.Library;component/SpriteBatch/SpriteBatch.ps", UriKind.Relative)).Stream;
_PixelShader = PixelShader.FromStream(_SilverGame.GraphicsDevice, shaderStream);
}
/// <summary>
/// 开始绘制
/// </summary>
/// <param name="SpriteBlendMode">Blend模式</param>
public void Begin(BlendState SpriteBlendMode)
{
Begin(SpriteBlendMode, _PixelShader);
}
/// <summary>
/// 开始绘制
/// </summary>
/// <param name="SpriteBlendMode">Blend模式</param>
/// <param name="pixelShader">像素着色器</param>
public void Begin(BlendState SpriteBlendMode, PixelShader pixelShader)
{
// 得到摄影坐标
_ViewMatrix = Matrix.Identity;
// 得到投影坐标
_ProjMatrix = Matrix.CreateOrthographicOffCenter(0, _SilverGame.ActualSize.X, _SilverGame.ActualSize.Y, 0, 0, 1);
Matrix viewprojMatrix = _ViewMatrix * _ProjMatrix;
// 设置Blend模式
_SilverGame.GraphicsDevice.BlendState = SpriteBlendMode;
// 设置顶点着色器
_SilverGame.GraphicsDevice.SetVertexShader(_VertexShader);
// 传入世界·摄影·投影矩阵参数(世界矩阵默认为单位矩阵)
_SilverGame.GraphicsDevice.SetVertexShaderConstantFloat4(0, ref viewprojMatrix);
// 设置像素着色器
_SilverGame.GraphicsDevice.SetPixelShader(pixelShader);
}
/// <summary>
/// 结束绘制
/// </summary>
public void End()
{
// 结束之前Flush一次全部精灵节点
Flush();
}
/// <summary>
/// 单帧投递
/// </summary>
/// <param name="DesRect">目标区域</param>
/// <param name="SurRect">纹理区域</param>
/// <param name="layerDepth">深度坐标</param>
/// <param name="Color">颜色值</param>
private void PostFrame(Rectangle DesRect, Rectangle SurRect, float layerDepth, Color Color)
{
// 新增精灵节点
_SpriteNodeList.Add(new SpriteNode(DesRect, SurRect, layerDepth, Color));
}
/// <summary>
/// 合并当前全部精灵节点的顶点缓冲及索引缓冲,一次性完成绘制
/// </summary>
private void Flush()
{
// 异常判别
if (_SpriteNodeList == null || _ActiveTexture == null || _SpriteNodeList.Count == 0)
{
return;
}
// 生成顶点缓冲数组
var vb = new VertexPositionColorTexture[_SpriteNodeList.Count * 4];
// 生成索引缓冲数组
var ib = new UInt16[_SpriteNodeList.Count * 6];
int i = 0;
foreach (SpriteNode node in _SpriteNodeList)
{
// 将纹理区域折合成uv坐标
float Txcrd_LU_u = node._SurRect.Left / _ActiveTexture.Width;
float Txcrd_LU_v = node._SurRect.Top / _ActiveTexture.Height;
float Txcrd_RU_u = node._SurRect.Right / _ActiveTexture.Width;
float Txcrd_RU_v = node._SurRect.Top / _ActiveTexture.Height;
float Txcrd_RD_u = node._SurRect.Right / _ActiveTexture.Width;
float Txcrd_RD_v = node._SurRect.Bottom / _ActiveTexture.Height;
float Txcrd_LD_u = node._SurRect.Left / _ActiveTexture.Width;
float Txcrd_LD_v = node._SurRect.Bottom / _ActiveTexture.Height;
// 填充顶点缓冲区数据
vb[i * 4] = new VertexPositionColorTexture(new Vector3(node._DesRect.Left, node._DesRect.Top, node._layerDepth), node._Color, new Vector2(Txcrd_LU_u, Txcrd_LU_v));
vb[i * 4 + 1] = new VertexPositionColorTexture(new Vector3(node._DesRect.Right, node._DesRect.Top, node._layerDepth), node._Color, new Vector2(Txcrd_RU_u, Txcrd_RU_v));
vb[i * 4 + 2] = new VertexPositionColorTexture(new Vector3(node._DesRect.Right, node._DesRect.Bottom, node._layerDepth), node._Color, new Vector2(Txcrd_RD_u, Txcrd_RD_v));
vb[i * 4 + 3] = new VertexPositionColorTexture(new Vector3(node._DesRect.Left, node._DesRect.Bottom, node._layerDepth), node._Color, new Vector2(Txcrd_LD_u, Txcrd_LD_v));
// 填充索引缓冲区数据
ib[i * 6] = (UInt16)(i * 4);
ib[i * 6 + 1] = (UInt16)(i * 4 + 1);
ib[i * 6 + 2] = (UInt16)(i * 4 + 2);
ib[i * 6 + 3] = (UInt16)(i * 4);
ib[i * 6 + 4] = (UInt16)(i * 4 + 2);
ib[i * 6 + 5] = (UInt16)(i * 4 + 3);
i++;
}
// 顶点缓冲、索引缓冲赋值
VertexBuffer _VertexBuffer = new VertexBuffer(_SilverGame.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vb.Length, BufferUsage.WriteOnly);
_VertexBuffer.SetData(0, vb, 0, vb.Length, 0);
IndexBuffer _IndexBuffer = new IndexBuffer(_SilverGame.GraphicsDevice, IndexElementSize.SixteenBits, ib.Length, BufferUsage.WriteOnly);
_IndexBuffer.SetData(0, ib, 0, ib.Length);
// 设置活动纹理
_SilverGame.GraphicsDevice.Textures[0] = _ActiveTexture;
// 设置顶点缓冲
_SilverGame.GraphicsDevice.SetVertexBuffer(_VertexBuffer);
// 设置索引缓冲
_SilverGame.GraphicsDevice.Indices = _IndexBuffer;
// 三角形绘制
_SilverGame.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vb.Length, 0, ib.Length / 3);
// 精灵节点清空
_SpriteNodeList.Clear();
}
public void Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle sourceRectangle, float layerDepth, Color color)
{
// 异常判断
if (texture == null)
return;
// _ActiveTexture第一次赋值
if (_ActiveTexture == null)
_ActiveTexture = texture;
// 如果当前纹理与活动纹理不同
if (_ActiveTexture != texture)
{
// 则Flush一次先前的全部节点
Flush();
// 更新活动纹理
_ActiveTexture = texture;
}
// 投递本帧
PostFrame(destinationRectangle, sourceRectangle, layerDepth, color);
}
// 一系列重载的Draw函数
public void Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle sourceRectangle, Color color)
{
Draw(texture, destinationRectangle, sourceRectangle, 0, color);
}
public void Draw(Texture2D texture, Rectangle destinationRectangle, Color color)
{
Draw(texture, destinationRectangle, new Rectangle(0, 0, texture.Width, texture.Height), color);
}
public void Draw(Texture2D texture, Vector2 position, Color color)
{
Draw(texture, new Rectangle((int)position.X, (int)position.Y, texture.Width, texture.Height), new Rectangle(0, 0, texture.Width, texture.Height), color);
}
}
}
因为是纯粹的3D硬件加速,所以跟传统Silverlight应用层面的Image相比,其优势是不言而喻的。这一点 园友 黯淡的橘子 已在其博文中给出了相关证明,大家可以参看他的文章~
我们简单的封装一下Silverlight资源加载的相关方法,构成一个内容管道的雏形,以便于后续功能扩展之用~
代码清单:ContentManager.cs
来自:http://www.cnblogs.com/kenkao
-------------------------------------*/
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
namespace Microsoft.Xna.Framework.Content
{
public class ContentManager
{
// SilverGame主体对象
SilverGame _SilverGame;
public SilverGame SilverGame
{
get
{
return _SilverGame;
}
}
/// <summary>
/// 构造方法
/// </summary>
/// <param name="game"></param>
public ContentManager(SilverGame game)
{
_SilverGame = game;
}
/// <summary>
/// 打开资源流
/// </summary>
/// <param name="uri">相对Uri</param>
/// <returns>资源流</returns>
public Stream OpenResourceStream(Uri uri)
{
return Application.GetResourceStream(uri).Stream;
}
/// <summary>
/// 打开资源流
/// </summary>
/// <param name="ProjectName">工程名</param>
/// <param name="uri">相对Uri(字符串形式)</param>
/// <returns>资源流</returns>
public Stream OpenResourceStream(string ProjectName, string uri)
{
return Application.GetResourceStream(new Uri(ProjectName + @";component/" + uri, UriKind.Relative)).Stream;
}
/// <summary>
/// 加载Texture2D
/// </summary>
/// <param name="stream">资源流</param>
/// <returns>所得Texture2D</returns>
public Texture2D LoadTexture2D(Stream stream)
{
Texture2D texture;
var image = new BitmapImage();
image.SetSource(stream);
texture = new Texture2D(_SilverGame.GraphicsDevice, image.PixelWidth, image.PixelHeight, false, SurfaceFormat.Color);
image.CopyTo(texture);
return texture;
}
/// <summary>
/// 加载顶点着色器
/// </summary>
/// <param name="stream">资源流</param>
/// <returns>所得顶点着色器</returns>
public VertexShader LoadVertexShader(Stream stream)
{
VertexShader vertexShader;
vertexShader = VertexShader.FromStream(_SilverGame.GraphicsDevice, stream);
return vertexShader;
}
/// <summary>
/// 加载像素着色器
/// </summary>
/// <param name="stream">资源流</param>
/// <returns>所得像素着色器</returns>
public PixelShader LoadPixelShader(Stream stream)
{
PixelShader pixelShader;
pixelShader = PixelShader.FromStream(_SilverGame.GraphicsDevice, stream);
return pixelShader;
}
}
}
这里的ContentManager只是一个雏形,肯定是没办法跟Xna原生态的ContentManager相提并论的 ^ ^
我们在SilverGame基类中声明一个ContentManager对象,以便令全部的子类持有这个对象:
public ContentManager Content
{ get { return _Content; } }
/// <summary>
/// 构造方法
/// </summary>
/// <param name="GameSurface">所关联的渲染表面</param>
public SilverGame(DrawingSurface GameSurface)
{
_GameSurface = GameSurface;
_Content = new ContentManager(this);
_ActualSize = new Vector2((float)_GameSurface.ActualWidth, (float)_GameSurface.ActualHeight);
// 自动为渲染表面关联必要的事件
_GameSurface.Loaded += new RoutedEventHandler(_GameSurface_Loaded);
_GameSurface.Unloaded += new RoutedEventHandler(_GameSurface_Unloaded);
_GameSurface.Draw += new EventHandler<DrawEventArgs>(_GameSurface_Draw);
_GameSurface.SizeChanged += new SizeChangedEventHandler(_GameSurface_SizeChanged);
// 虚函数调用——初始化
this.Initialize();
}
然后是主体代码:
代码清单:Game.cs
来自:http://www.cnblogs.com/kenkao
-------------------------------------*/
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.IO;
namespace SilverXna
{
public class Game : SilverGame
{
SpriteBatch _SpriteBatch;
Texture2D texture;
public Game(DrawingSurface GameSurface)
: base(GameSurface)
{ }
public override void Initialize()
{
base.Initialize();
}
public override void LoadContent()
{
_SpriteBatch = new SpriteBatch(this);
Stream imageStream = Content.OpenResourceStream("SilverXna","Content/SLXNA.png");
texture = Content.LoadTexture2D(imageStream);
base.LoadContent();
}
public override void UnloadContent()
{
base.UnloadContent();
}
public override void Update(TimeSpan DeltaTime, TimeSpan TotalTime)
{
base.Update(DeltaTime, TotalTime);
}
public override void Draw(TimeSpan DeltaTime, TimeSpan TotalTime)
{
GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, new Color(100, 149, 237, 255), 1.0f, 0);
_SpriteBatch.Begin(BlendState.AlphaBlend);
_SpriteBatch.Draw(texture, new Vector2(100, 100), new Color(255, 255, 255, 255));
_SpriteBatch.End();
base.Draw(DeltaTime, TotalTime);
}
}
}
SpriteBatch的用法跟原生态的Xna环境下的用法是一模一样的,相关绘制方法可以在现有基础上随意重载扩展,并且兼容Shazzam全部的ps特效,你只需借助Content对象Load得到相应的PixelShader,而后传入SpriteBatch重载的Begin函数中即可 ^ ^
最后是效果图:
虽然目前Silverlight5 RC版本下的Xna框架还不够尽善尽美,但我们看到的是光明的前景,未来值得期待 ^ ^
以上,谢谢~
=============================================
>> Silverlight5 RC资源下载及工具包语言版本冲突问题的解决方法:
http://space.cnblogs.com/group/topic/49727/